Search code examples
rustiteratorlifetime

What lifetime to use with Iterator for a hierarchy of `Rc`s?


I've got a Rust struct that represents a hierarchical data structure and each struct contains a Vec<Rc<Self>> that creates the hierarchical structure. I want to create an iterator that iterates over Rcs to every member of the hierarchy, starting with a given node. The Node struct has a lifetime parameter 'a because the entire hierarchy refers some &'a strs from a text document that the whole hierarchy is based on (contained inside NodeDetail<'a>).

pub struct Node<'a> {
    pub detail: NodeDetail<'a>,
    pub children: Vec<Rc<Self>>,
}

impl<'a> Node<'a> {
    // Returns an iterator over `Rc`s to all the children
    pub fn iter(&self) -> Box<(dyn Iterator<Item=Rc<Self>> + 'static)> {
        let mut ans:Box<(dyn Iterator<Item=Rc<Self>> + 'static)> = Box::new(std::iter::empty());

        for c in self.children.iter() {
            ans = Box::new(ans.chain(std::iter::once(c.clone())));
            ans = Box::new(ans.chain(c.iter()));
        }

        ans
    }
}

With this attempt, I'm getting this error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/model/updm/impl_common.rs:23:40
   |
23 |             ans = Box::new(ans.chain(c.iter()));
   |                                        ^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 5:6...
  --> src/model/updm/impl_common.rs:5:6
   |
5  | impl<'a> super::UpdmCommon<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/model/updm/impl_common.rs:23:40
   |
23 |             ans = Box::new(ans.chain(c.iter()));
   |                                        ^^^^
   = note: expected `&UpdmCommon<'_>`
              found `&UpdmCommon<'a>`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
  --> src/model/updm/impl_common.rs:23:19
   |
23 |             ans = Box::new(ans.chain(c.iter()));
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `Box<(dyn Iterator<Item = Rc<UpdmCommon<'a>>> + 'static)>`
              found `Box<dyn Iterator<Item = Rc<UpdmCommon<'a>>>>`

I think it's somehow confusing the lifetime that the Node struct is generic over with a lifetime specific to the iterator. I'm cloning the Rcs so the Item of the iterator is owned and has a 'static lifetime. I think the iterator itself should also be owned, which would make it 'static. I first tried Box<dyn Iterator<Item=Rc<Self>>>, then added the 'static annotation to try to fix the first errors I got. Anyone know how to fix this?


Solution

  • You need to tie the lifetime of the value you're returning from iter() to &self and you need to cast the boxed iterator to a trait object.

    use std::rc::Rc;
    
    pub struct NodeDetail<'a>(&'a str);
    
    pub struct Node<'a> {
        pub detail: NodeDetail<'a>,
        pub children: Vec<Rc<Self>>,
    }
    
    impl<'a> Node<'a> {
    
        // Returns an iterator over `Rc`s to all the children
        pub fn iter(&'a self) -> Box<(dyn Iterator<Item=Rc<Self>> + 'a)> {
            let mut ans = Box::new(std::iter::empty()) as Box<dyn Iterator<Item=Rc<Self>>>;
    
            for c in self.children.iter() {
                
                ans = Box::new(ans.chain(std::iter::once(c.clone())));
                ans = Box::new(ans.chain(c.iter()));
            }
    
            ans
        }
    
    }