Lifetime conflict

I try to write protection for vector reallocation:

pub struct AppendIter<'t, 's, T> {
    target: &'t mut Vec<T>,
    source: &'s [T],
    cap: usize,
    pos: usize,
}

impl<'t, 's: 't, T: Clone> Iterator for AppendIter<'t, 's, T> {
    type Item = &'t [T];
    fn next(&mut self) -> Option<Self::Item> {
        let free = self.cap - self.target.len();
        let takes = self.source.len() - self.pos;
        let len = self.target.len();

        // Taget not empty and not full
        if len > 0 && len < self.cap {
            if takes > 0 {
                self.target
                    .extend_from_slice(&self.source[..takes.min(free)]);
                self.pos += takes.min(free);
            }

            if free > takes {
                None
            } else {
                Some(self.target.as_slice())
            }
        // Iterate by Source
        } else {
            if takes < self.cap {
                self.target.clear();
                self.target.extend_from_slice(&self.source[self.pos..]);
                self.pos += takes;
                None
            } else {
                let pos = self.pos;
                self.pos += self.cap;
                Some(&self.source[pos..self.pos])
            }
        }
    }
}

and get compiler error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lib.rs:26:34
   |
26 |                 Some(self.target.as_slice())
   |                                  ^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 10:5...
  --> src/lib.rs:10:5
   |
10 |     fn next(&mut self) -> Option<Self::Item> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:26:22
   |
26 |                 Some(self.target.as_slice())
   |                      ^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'t` as defined on the impl at 8:6...
  --> src/lib.rs:8:6
   |
8  | impl<'t, 's: 't, T: Clone> Iterator for AppendIter<'t, 's, T> {
   |      ^^
note: ...so that the types are compatible
  --> src/lib.rs:10:46
   |
10 |       fn next(&mut self) -> Option<Self::Item> {
   |  ______________________________________________^
11 | |         let free = self.cap - self.target.len();
12 | |         let takes = self.source.len() - self.pos;
13 | |         let len = self.target.len();
...  |
40 | |         }
41 | |     }
   | |_____^
   = note: expected `Iterator`
              found `Iterator`

I do not understand - what's wrong?
playground

It's because the reference is mutable. If you have an &'short &'long mut T, then that only allows you to get a &'short T, and not a &'long T. This applies in your case, because the (hidden) lifetime on &mut self in next acts as 'short, but the lifetime you wanted is 't, which acts as 'long.

If the reference was immutable, then there would be no problem, as &'short &'long T can be turned into a &'long T.

3 Likes

This is a usual "surprise" that happens when dealing with Mut iterators: many people have the mental model of Iterator::next() having the following signature:

trait StreamingIteratorGAT {
    type Item<'next>;
    fn next<'next> (self: &'next mut Self) -> Self::Item<'next>;
}
  • (this is currently an experimental syntax, but one can write an equivalent using:

    trait StreamingIterator<'next> : 'next {
        type Item;
        fn next (self: &'next mut Self) -> Self::Item;
    }
    

The difference with the classic:

trait Iterator {
    type Item;
    fn next<'next> (self: &'next mut Self) -> Self::Item;
}

may be hard to spot, but is actually quite crucial: it all relies on whether the yielded Items can depend on 'next, the lifetime of the &mut borrow of the iterator used for the .next() call.

This semantic difference is easier to understand by viewing exclusive borrows (&mut) as a compile-time Mutex::lock():

  • to call .next(), since it takes a &mut Self, receiver, we need to hold a .lock() during that .next() call at least. I've even given a name to that "duration" for which the lock is held: 'next.

  • In the classic Iterator trait, the type Item cannot depend on / know about 'next, since the type is defined before that "duration parameter" is ever introduced. This means that the following is a valid "sequence of actions":

    1. obtain an exclusive lock on the iterator;

    2. while the lock is held, call .next() and obtain a yielded Item.

    3. release the exclusive lock.

    4. keep using that Item even if the lock has been released (e.g., you can append it to some Vec; and rince and repeat these steps: once the iterator is exhausted, you will have .collect()ed all its items within that Vec).

    In other terms, with the classic Iterator trait, the obtained items can escape the scope of the for loop block.

  • That's the main difference with the StreamingIteratorGAT and for<'next> StreamingIterator<'next> traits.

    In both those patterns, the type Item is allowed to depend on 'next, which means the actual type may be infected with that lifetime, which in that case means that the obtained Item can only be used while the lock is held: the moment the lock is released, it's invalid to keep using the Item:

    1. obtain an exclusive lock on the iterator;

    2. while the lock is held, call .next() and obtain a yielded Item<'next>.

    3. release the exclusive lock. _This ends 'next, and thus, allows the yielded Item<'next> to dangle.

    4. That Item is thus unusable (e.g., if you want to call .next() a second time, since that requires that the lock for the next call have been released (otherwise it "would deadlock" / lead to a Rust complaining about attempting to obtain a &mut borrow while another one is held), it means that the moment you obtain the second item, the first item is unusable: you can only have access at one item at a time).

As you can see, the StreamingIterator API is far more limiting from a user-of-the-trait / user-of-the-iterator perspective, but that also means it is easier to implement / less restrictive for a an implementor-of-the-trait / producer-of-the-iterator.


Going back to your concrete example (which I will simplify to using a single lifetime for AppendIter):

pub struct AppendIter<'iter, T> {
    target: &'iter mut Vec<T>,
    source: &'iter [T],
    cap: usize,
    pos: usize,
}

impl<'iter, T: Clone> Iterator for AppendIter<'iter, T> {
    type Item = &'iter [T];

    // Tip: do not use `&self` /`&mut self` sugar if you are confused about
    // lifetimes: force yourself to spelling out the full type signature.
    fn next<'next> (self: &'next mut AppendIter<'iter, T>)
      -> Option<&'iter [T]>
//              ^^^^^^
//              returned item must be borrowed as long as `'iter`.
    {
        …
        //           can only be borrowed for `'next`.
        //           vvvv
        return Some(&self.target[..]);
    }
}

So, as you can see, your implementation is making you return a &self.target… which is thus infected with the 'next lifetime (&'next &'iter mut Vec<T>; as @alice mentioned, Rust can "flatten" the borrows for you by using the shorter one: &'next Vec<T> and finally, &'next [T]).

And yet you have to return something with the 'iter lifetime because you are using the classic Iterator trait, which requires your yielded items to be allowed to coexist with each other and with other .next() calls (that's what returning &'iter [T] instead of &'next [T] implies).

And Rust has a problem with the latter point: during a follow-up .next() call, you may very well mutate the Vec, and even cause a reallocation: that would invalidate previous borrows / pointers to the backing heap-allocated buffer, so some implementations could cause dangling pointers. The fact that your implementation may not cause them because it checks for capacity etc. is ignored by Rust, since:

  • type / function signatures are implementation-agnostic (so that if you were to change the implementation later on, you can't cause compile-time breakage on code that depended on it).

  • you haven't "lied" / cheated in the type signature by telling Rust to trust your implementation above all other things, by using unsafe (and in case this is isn't clear: one should refrain from using unsafe to circumvent lifetime problems they don't understand; lifetimes and their compile-time errors are there to prevent bugs, and in this instance, it is very very easy to feature an incorrect / unsound / buggy API if you still try to yield &'iter [T] slices instead of &'next [T] slices).

So I'd advise for trying to implement the StreamingIterator API, although, for starters, let's get rid of the complex trait signature and generalization: these kind of traits with "associated types depending on lifetimes" are annoying and hard to write at best, and at worst the compiler bugs with them leading to false positives (compiler refusing to compile correct code) or even compiler crashes.

But without any trait abstraction, the signature is quite simple:

- impl<'iter, T: Clone> Iterator for AppendIter<'iter, T> {
+ impl<'iter, T: Clone>              AppendIter<'iter, T> {
-     type Item = &'next [T];

      fn next<'next> (self: &'next mut AppendIter<'iter, T>)
-       -> Option<&'iter [T]>
+       -> Option<&'next [T]>
      { … } // your implementation here
  }

And you'll be able to use this smarter iterator by doing stuff like:

while let Some(item) = append_iter.next() {
    …
} // <- `item` cannot be used outside this scope (that's the difference with classic `Iterator::next()`)

so, by the time we go back to another iteration of the loop and another call to .next(), Rust guarantees us that item will never be used again, so we are free to mutate the Vec and cause reallocations, without any dereferences of the dangling pointers ever happening.

4 Likes

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.