Mind Twisting Lifetimes With IntoIterator

I'm trying to mess with the Query system of Bevy, and I'm running into lifetime issue that I've tried to simplify and reproduce here. This is a rather contrived example, but I think accurately reproduces the error I'm getting.

Can anybody give some pointers on this?

trait Fetch<'a> {
    type Item;
    
    fn get(strings_from_world: &'a Vec<String>, index: usize) -> Self::Item;
}

struct Data<'world, F> {
    strings_from_world: &'world Vec<String>,
    _marker: core::marker::PhantomData<F>
}

impl<'world, F: Fetch<'world>> Data<'world, F> {
    fn iter<'data>(&'data mut self) -> DataIter<'world, 'data, F> {
        DataIter {
            data: self,
            index: 0,
        }
    }
}

struct DataIter<'data, 'world, F> {
    data: &'data Data<'world, F>,
    index: usize,
}

impl<'data, 'world, F: Fetch<'world>> Iterator for DataIter<'data, 'world, F> {
    type Item = <F as Fetch<'world>>::Item;
    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

impl<'data, 'world, F: Fetch<'world>> IntoIterator for &'data mut Data<'world, F> {
    type IntoIter = DataIter<'data, 'world, F>;
    type Item = <F as Fetch<'world>>::Item;
    
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:38:14
   |
38 |         self.iter()
   |              ^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'data` as defined on the impl at 33:6...
  --> src/main.rs:33:6
   |
33 | impl<'data, 'world, F: Fetch<'world>> IntoIterator for &'data mut Data<'world, F> {
   |      ^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:38:9
   |
38 |         self.iter()
   |         ^^^^
note: but, the lifetime must be valid for the lifetime `'world` as defined on the impl at 33:13...
  --> src/main.rs:33:13
   |
33 | impl<'data, 'world, F: Fetch<'world>> IntoIterator for &'data mut Data<'world, F> {
   |             ^^^^^^
note: ...so that the types are compatible
  --> src/main.rs:37:42
   |
37 |       fn into_iter(self) -> Self::IntoIter {
   |  __________________________________________^
38 | |         self.iter()
39 | |     }
   | |_____^
   = note: expected `std::iter::IntoIterator`
              found `std::iter::IntoIterator`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

You need to say that 'data is smaller than 'world.

impl<'data, 'world, F: Fetch<'world>> IntoIterator for &'data mut Data<'world, F>
where
    'data: 'world,
{
    type IntoIter = DataIter<'data, 'world, F>;
    type Item = <F as Fetch<'world>>::Item;
    
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
2 Likes

Aha! That worked for the small example. I'll try that in my actual code. Thanks!

That makes a lot of sense to me.

I got this to compile:

trait Fetch {
    type Item;
    fn get(strings_from_world: &Vec<String>, index: usize) -> Self::Item;
}

struct Data<'a, F> {
    strings_from_world: &'a Vec<String>,
    _pd: core::marker::PhantomData<F>
}

impl<'a, 'b: 'a, 'c: 'b, F: Fetch> Data<'a, F> {
    fn iter(&'c mut self) -> DataIter<'a, 'b, F> {
        DataIter {
            data: self,
            index: 0,
        }
    }
}

struct DataIter<'a, 'b: 'a, F> {
    data: &'a Data<'b, F>,
    index: usize
}

impl<'a, 'b: 'a, F: Fetch> Iterator for DataIter<'a, 'b, F> {
    type Item = <F as Fetch>::Item;
    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

impl<'a, 'b: 'a, 'c: 'b, F: 'b + Fetch> IntoIterator for &'a mut Data<'b, F> where Self: 'c{
    type IntoIter = DataIter<'a, 'b, F>;
    type Item = <F as Fetch>::Item;
    
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    
}

'data: 'world doesn't this read "data lasts atleast as long as world"?

2 Likes

Ah, you're right. The bound makes them equal. The actual mistake is this:

 impl<'world, F: Fetch<'world>> Data<'world, F> {
+    fn iter<'data>(&'data mut self) -> DataIter<'data, 'world, F> {
-    fn iter<'data>(&'data mut self) -> DataIter<'world, 'data, F> {
         DataIter {
             data: self,
             index: 0,
         }
     }
 }
3 Likes

Looks like it worked so far in my larger codebase as well. Thanks!

Good catch.

OK, so what if data doesn't live at least as long as world?

I tried to create this with this test:

( playground )

fn main() {
    let world = vec![String::from("hello"), String::from("world"), String::from("goodbye")];
    {
        let mut data = Data {
            strings_from_world: &world,
            _marker: core::marker::PhantomData::<Fetcher>::default(),
        };
        
        let data_iter = (&mut data).into_iter();
        
        for s in data_iter {
            println!("{}", s);
        }
    } // data dropped here
} // World droped here

In this example data is dropped before world so it doesn't live at least as long as world, but it still works.

:question: :question: :question:

I didn't do anything unsafe, so I figured that I shouldn't be able to break it, but how does that make sense.

Data gets an immutable pointer to world. World is in a longer-lifetime closure than data. In other words, world: 'data (world lasts ATLEAST as long as data, or world >= data). When data drops, it loses the immutable reference, but that doesn't mean world is dropped.

Also, sometimes using lifetime names can make things confusing. This is why I use 'a, 'b: 'a, and 'c: 'b (this means that c lasts the longest, then b, then a. However, it is also possible through transitivity of equality that they last the same amount of time).

Lifetimes are relative (except 'static, that's absolute)

1 Like