Search code examples
rustiteratorlifetimetrait-objects

Storing an iterator over borrowed refs inside a Struct


I'm new to Rust and like many finding lifetimes quite hard to get the hang of. I get the basics, but I can't quite fathom how to make this work.

I'm trying to implement an abstract data structure that holds a collection of records. One requirement is that the collection of records referenced by the data structure is represented as an Iterator, because at times the records will be read from disk, at other times they'll come from memory, and at other times somewhere like a cache. The data structure itself doesn't care how the records are returned. It simply needs to be able to iterate over them and filter/map them etc.

My actual code is more involved than this, but I've distilled this down as much as possible to illustrate the issue I'm hitting.

Let's say I have a type Record. It is not important what that is. If I have a Vec<Record> called records, then I can get an iterator over &Record instances, using records.iter() right? That's exactly what I need to store in a struct field, except that it needs to be abstract and not tied specifically to the implementation of Vec<T>.

I have tried to define a trait as such:

trait RecordIterator: Iterator<Item = &Record> {}

And the struct that holds trait objects of this type:

struct RecordSet {
    records_iter: Box<dyn RecordIterator>,
}

Of course this doesn't compile because I need to specify the lifetimes. The compiler suggests using higher rank lifetimes, so:

trait RecordIterator: for<'a> Iterator<Item = &'a Record> {}

impl<T> RecordIterator for T where T: for<'a> Iterator<Item = &'a Record> {}

But this gives me an error:

   Compiling playground v0.0.1 (/playground)
error[E0582]: binding for associated type `Item` references lifetime `'a`, which does not appear in the trait input types
 --> src/main.rs:3:40
  |
3 | trait RecordIterator: for<'a> Iterator<Item = &'a Record> {}
  |                                        ^^^^^^^^^^^^^^^^^

error[E0582]: binding for associated type `Item` references lifetime `'a`, which does not appear in the trait input types
 --> src/main.rs:5:56
  |
5 | impl<T> RecordIterator for T where T: for<'a> Iterator<Item = &'a Record> {}
  |                                                        ^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0582`.
error: could not compile `playground` due to 2 previous errors

Here is a link to the playground containing this code.

What do I need to do to store this kind of Iterator in the struct (as long as it is valid)? I'm also happy to avoid using Vec<T>::iter() entirely and construct a new Iterator that basically does the same thing but meets the compiler's expectations if that is necessary.


Solution

  • A higher-ranked lifetime cannot appear only in associated types without appearing in generics too. I don't think this is impossible to implement, as far as I understand it was allowed once but was implemented incorrectly, causing unsoundness because the trait solver was sometimes choosing different lifetimes for the same instance. So it was just forbidden. I don't know if it's even hard to implement, or just nobody bothered.

    But this doesn't matter because your definition is incorrect anyway. Take for example std::slice::Iter<'a, Record>, created if you do vec.iter(). This struct does not implement Iterator<Item = &'b Record> for any lifetime 'b, only for the lifetime 'a - the lifetime of the vector. So what you need is a generic parameter, and not a higher-ranked trait bound:

    pub trait RecordIterator<'a>: Iterator<Item = &'a Record> {}
    
    impl<'a, T> RecordIterator<'a> for T where T: Iterator<Item = &'a Record> {}
    
    pub struct RecordSet<'a> {
        records_iter: Box<dyn RecordIterator<'a> + 'a>,
    }