Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on Recursive traversal of composite tree of mutable "trait objects"?

Parent

Recursive traversal of composite tree of mutable "trait objects"?

+3
−0

I'm working on a background service/daemon for an embedded device, in Rust. The daemon manages several hardware components and these are structured using the Composite design pattern. The composite type from the pattern is called Component in the service:

pub trait Component:
    // ...
    + Debug
    + Sync
    + Send
    + 'static
{
    fn components(&self) -> Vec<&dyn Component> {
        vec![]
    }

    fn components_mut(&mut self) -> Vec<&mut dyn Component> {
        vec![]
    }
}

There are several different concrete types that implement this trait. For example:

#[derive(Debug)]
pub struct KeypadModel {
    components: Vec<Box<dyn Component>>,
}
// ...
impl KeypadModel {
    pub fn new() -> Self {
        debug!("Creating {} ...", stringify!(KeypadModel));
        Self {
            components: vec![
                Box::new(ButtonGroupModel::new()),
                Box::new(NumberpadModel::new()),
            ],
        }
    }
}

The problem is that the elements of the components vector are not concrete types; they must be trait objects. To clarify what I'm after, I have a working example using same concrete type, but the actual system needs a version that works with trait objects.

So, I'm left with something like this (which doesn't work):

impl Component for KeypadModel {
    fn components(&self) -> Vec<&dyn Component> {
        fn subtree(root: &dyn Component) -> Vec<&dyn Component> {
            let mut v = vec![];
            root.components  // !! Error: See below !!
                .iter()
                .for_each(|m| v.append(&mut Self::subtree(m)));
            v.push(root);
            v
        }

        let mut v = vec![];
        self.components
            .iter()
            .for_each(|m| v.append(&mut Self::subtree(m)));
        v
    }

    fn components_mut(&mut self) -> Vec<&mut dyn Component> {
        todo!() // ??
    }
}

The error is that the code wants/expects root.components(), which is the method that belongs to the trait, but, I need access to the root.components field, which belongs to the concrete type.

The question is: How can I traverse[1] the trait object tree structure, starting at an arbitrary root node, to generate a vector of all[2] descendants?

Please, note:

  1. Trying to use the root.components() method shows the following error:
Diagnostics:
   cannot return value referencing temporary value
   returns a value referencing data owned by the current function [E0515]
  1. Even if it weren't an error, the recursive logic would be incorrect, b/c deeper leaf nodes would be added more than once. (This is easier for me to show with an initial Python prototype I had, which can be run at https://playground.programiz.com)

  2. The composite/container types hold different concrete types in their components vector, which is why the vector must work with trait objects.

  3. Modifying the Component trait and/or concrete types implementing it might be feasible.


  1. Recursively is likely more natural, but an iterative solution is also OK. ↩︎

  2. Including composite/non-leaf nodes. ↩︎

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+3
−0

A root.components field need not exist, so I'm not sure what you're hoping for. Also, I don't know if it's an issue with your description or your code, but the code will only return mutable references to "leaf" Components while your description talks about "all descendants". It's not clear if "all descendants" is intended to include Components that have children as well.

As far as I can tell, changing to:

match root.components().is_empty() {
    true => v.push(root),
    false =>
        root.components_mut()
            .into_iter()
            .for_each(|c| v.append(&mut subtree_mut(c))),
}

would compile and work fine other than the unnecessary allocation of the result of root.components() which could be fixed by adding a method to Component to test for this case without needing to return a Vec. This could probably be finessed some other way.

As for

Using root.components_mut() would create a temporary value owned by the function and trying to return that is an error.

This doesn't matter. This temporary's lifetime isn't really connected to anything. Just the contents of the Vec are attached to the relevant lifetimes. In the above code, this is addressed by consuming that intermediate via into_iter() and then just passing c directly since it is the mutable ref we want.

And for

Even if it weren't an error, the recursive logic would be incorrect and several objects would be added more than once.

I don't quite understand what you're getting at here. In the code I list above, mutable references to all "leaf" Components will be returned (assuming no cycles...). If a Component is a child of multiple Components, then its "leaves" will be included multiple times. If this is a problem, then it wouldn't be resolved by directly accessing some component field. You need to detect visited Components and avoid reprocessing them. This could be done by having a visited field that is set or by having a set of visited Components that gets passed along. This technically can't happen, since it would require a violation of the borrowing rules to get into this situation. That said, if you did want a non-tree-like graph to be possible, you'd need to use something like Rc. Even without that though, you'd likely need something like Rc to be able to return all mutable references to all descendants, since mutably borrowing a KeypadModel would preclude mutably borrowing any element of its component field.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

1 comment thread

I'll answer and/or clarify what I can. > not sure what you're hoping I've updated OP with a pla... (6 comments)
I'll answer and/or clarify what I can. > not sure what you're hoping I've updated OP with a pla...
ghost-in-the-zsh‭ wrote 6 months ago

I'll answer and/or clarify what I can.

not sure what you're hoping

I've updated OP with a playground link.

code will only return [..] "leaf"

Sorry. That was my mistake on the mutable traversal. I had a larger set of objects (in the concrete types version) when testing and didn't realize a few of them were missing. For the playground link, I've verified that the immutable traversal is correct. I haven't figured out how to deal with the mutable traversal correctly. (e.g. I need a v.push(root) line, but this causes E0499 due to a double mut borrow that I'm not yet sure how to work around.)

"all descendants" [..] include[s] Components [with] children

It's intended to include them. I had an error in the mutable impl.

would compile and work

Unless there's a misunderstanding, it does not. I get this:

cannot return value referencing temporary value
returns a value referencing data owned by the current function [E0515]
ghost-in-the-zsh‭ wrote 6 months ago

could be fixed by [..] without needing to return a Vec [..]

Could you clarify what you mean?

This doesn't matter [..] consuming that intermediate via into_iter() and then just passing c directly since it is the mutable ref we want.

Using into_iter() causes other issues, unless you mean adding into_iter() after the first iter() call, which does work.

I don't quite understand

This is easier for me to show with an initial Python prototype I had, which can be run at https://playground.programiz.com

assuming no cycles

Correct.

you'd likely need something like Rc [..]

Will look into this.

I apologize for any other issues I may've missed. It's been a long night/week. Thanks again.

Derek Elkins‭ wrote 6 months ago

Just to be clear, I did actually take the code from your (original) post and got it to successfully compile with the changes I mentioned.

As for your updated version, it falls under what I mentioned at the end referencing Rc. You will not be able to make a version of components_mut that returns the same references as components only mutable. This would lead to multiple mutable references to the same object. Having a mutable reference to a struct implies having a mutable reference to its fields. Ultimately, you'll probably need (and want) something like Rc<RefCell<dyn Component>>, see https://doc.rust-lang.org/book/ch15-05-interior-mutability.html assuming you want this method at all.

ghost-in-the-zsh‭ wrote 6 months ago

Just to be clear, I did actually take the code from your (original) post and got it to successfully compile with the changes I mentioned.

I think we may be talking past each other a bit, but I'm not sure where the (possible) misunderstanding crept in. Would you mind sharing a playground link with the changes you mentioned?

Derek Elkins‭ wrote 6 months ago

It doesn't seem relevant. The changes I made were for your original version of the code which didn't do what you intended. As mentioned in my answer and my previous comment, it is simply not possible to implement the function you want (without unsafe) with the type you provided. The borrow rules disallow having a vector like vec![&mut x, &mut x.components[0]]. You'll need to use something like RefCell. This turns the compile-time mutable borrow checks into runtime checks. You would likely need to change the definition of types like KeypadModel to use RefCell instead. You can probably get away without Rc though that may limit what you can do down the line. Ultimately, you need to decide what patterns of sharing and mutation you want to allow and then choose a type appropriate for that. This is a design decision most programming languages don't require you to make. Rc<RefCell<T>> is roughly equivalent to what most languages do, but it has overhead and weaker guarantees.

ghost-in-the-zsh‭ wrote about 2 months ago

I'll add this last comment to note that the suggestion in this post skips the composite nodes and adds only the leaves, which deviates from the desired behavior mentioned in the OP. (See footnote #2.)