Clone iterator behind a box

I'm trying to make an enum store any kind of iterator defined as:

pub enum TraverserResult<'a> {
    NodesIter(Box<dyn Iterator<Item = (&'a NodeId, &'a NodeRef)> + 'a>),
    Empty
}

The idea is that I don't want to enumerate all possible Iterators forms (Iter, Cloned, Filter...).
And I want to use it like that:

 fn execute_filter_with_label(&self, previous: &TraverserResult, label: &String) -> TraverserResult {
      match previous {
          TraverserResult::NodesIter(iter) => {
              let res = iter.filter(|(_, node)| node.has_label(label));
              TraverserResult::NodesIter(Box::new(res))
          },  
          TraverserResult::Empty => {
              TraverserResult::Empty
          }   
      }   
  }   

But when I compile I get the following error:

--> src/graph_engine/traversal/traversal.rs:118:17
|
114 | fn execute_filter_with_label(&self, previous: &TraverserResult, label: &String) -> TraverserResult {
| --------------- ---------------
| |
| this parameter and the return type are declared with different lifetimes...
...
118 | TraverserResult::NodesIter(Box::new(res))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...but data from previous is returned here

And I understand why but how do I copy an iterator so I can reuse it? The code is called from:

 pub fn execute(&self) -> TraverserResult {
      let mut res = TraverserResult::Empty;

      for step in &self.steps {
          res = match step {
              TraverserStep::GetNodes => {
                  self.execute_nodes()
              }   
              TraverserStep::FilterWithLabel(label) => {
                  self.execute_filter_with_label(&res, &label)
              }   
          };  
      }   

      res 
  }

Thanks for help.

Your error is happening because your return type has the same lifetime as &self (because of the lifetime elision rules), while it should actually have the lifetime of previous and label. The solution is to give them the same lifetime explicitly:

fn execute_filter_with_label<'a>(
    &self,
    previous: &TraverserResult<'a>,
    label: &'a String,
) -> TraverserResult<'a>

However, after this change you'll run into some new errors, like:

error[E0507]: cannot move out of `*iter` which is behind a shared reference
  --> src/lib.rs:23:27
   |
23 |                 let res = iter.filter(|(_, node)| node.has_label(label));
   |                           ^^^^ move occurs because `*iter` has type `std::boxed::Box<dyn std::iter::Iterator<Item = (&(), &NodeRef)>>`, which does not implement the `Copy` trait

The easiest solution would be to take previous by value instead of by reference, so that you can move everything into the new iterator. However, this will consume previous:

fn execute_filter_with_label<'a>(
    &self,
    previous: TraverserResult<'a>,
    label: &'a String,
) -> TraverserResult<'a> {
    match previous {
        TraverserResult::NodesIter(iter) => {
            // also note `move` closure
            let res = iter.filter(move |(_, node)| node.has_label(label));
            TraverserResult::NodesIter(Box::new(res))
        }
        TraverserResult::Empty => TraverserResult::Empty,
    }
}

(Playground)

If instead you need to clone previous, so the old iterator and the new one are both alive afterward, you'll find the regular Clone trait doesn't work for trait objects. You can use the alternative trait from the dyn-clone crate, or roll your own as described in this thread.

3 Likes

Thanks a lot this solved my issue. At some point I already tried to passing by value, but I forgot to put the lifetime on the previous parameter...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.