Search code examples
iteratorrustlifetimeborrow-checker

Am I incorrectly implementing IntoIterator for a reference or is this a Rust bug that should be reported?


Furthering the example of implementing IntoIterator for a wrapped vector as per the Rust book, I am also trying to implement IntoIterator for a reference to the wrapper, as per the following code (Playground link):

struct VecWrapper(Vec<i32>);

impl VecWrapper {
    fn iter(&'static self) -> Iter {
        Iter(Box::new(self.0.iter()))
    }
}

struct Iter(Box<Iterator<Item = &'static i32>>);

impl Iterator for Iter {
    type Item = &'static i32;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

impl IntoIterator for &'static VecWrapper {
    type Item = &'static i32;
    type IntoIter = Iter;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    // let test = vec![1, 2, 3]; // obviously, works
    let test = VecWrapper(vec![1, 2, 3]); // not working
    for v in &test {
        println!("{}", v);
    }
}

Although the implementation compiles, the attempt to use it in main doesn't with the following error:

error[E0597]: `test` does not live long enough
  --> src/main.rs:31:14
   |
31 |     for v in &test {
   |              ^^^^^
   |              |
   |              borrowed value does not live long enough
   |              argument requires that `test` is borrowed for `'static`
...
34 | }
   | - `test` dropped here while still borrowed

This code is greatly simplified from what I would actually want to use as to using only 'static lifetimes, using an existing contained type, and using i32 for the inner (iterated) type, but it is boiled down to show just the problem.


The accepted answer solves the first part of the problem as to not using 'static and using + 'a with traits. I still am having a problem with the actual code, which is a LazyList implementation. I've posted that as Am I incorrectly implementing IntoIterator for a reference to a LazyList implementation or is this a Rust bug?.


Solution

  • You have correctly implemented the iterator for references to VecWrappers that live for the entire length of the program — the 'static lifetime.

    Chances are you want to have a generic lifetime. That lifetime will then be provided a concrete lifetime, unique to each instantiation. Usually, we are lazy and just give this lifetime the name 'a:

    struct VecWrapper(Vec<i32>);
    
    impl VecWrapper {
        fn iter(&self) -> Iter {
            Iter(Box::new(self.0.iter()))
        }
    }
    
    struct Iter<'a>(Box<dyn Iterator<Item = &'a i32> + 'a>);
    
    impl<'a> Iterator for Iter<'a> {
        type Item = &'a i32;
    
        fn next(&mut self) -> Option<Self::Item> {
            self.0.next()
        }
    }
    
    impl<'a> IntoIterator for &'a VecWrapper {
        type Item = &'a i32;
        type IntoIter = Iter<'a>;
    
        fn into_iter(self) -> Self::IntoIter {
            self.iter()
        }
    }
    
    fn main() {
        let test = VecWrapper(vec![1, 2, 3]);
        for v in &test {
            println!("{}", v);
        }
    }
    

    Important changes:

    • Box<dyn Iterator<Item = &'a i32> + 'a> - + 'a was added. This is needed because a trait object will assume that there are no interior values that reference anything with a short lifetime.
    • The Item type is now &'a i32.
    • A generic lifetime is declared in many places and provided in many others (<'a>).

    See also:


    Normally, there wouldn't be a reason to use a trait object here. I'd just embed the iterator directly:

    struct Iter<'a>(std::slice::Iter<'a, i32>);
    

    This avoids the need to have any indirection, which is unused in this case anyway. Additionally, it more obviously couples the lifetimes.