Lifetime problems trying to convert a slice in a returned iterator

I've created a small prototype in the playground.

What I'm trying to do is to store the inner components of a type that implements the Chunkable trait, in a Collection, and later be able to iterate over those components grouped in chunks, converted into the original type.

The last bit is what I'm struggling with. After following the compiler indications I've got to the point where I need some help understanding these lifetime issues.

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/main.rs:64:18
   |
64 |         self.vec.chunks(self.chunk_size).map(|c| c.into())
   |                  ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 61:6...
  --> src/main.rs:61:6
   |
61 | impl<'a, C: Chunkable<T> + From<&'a [T]>, T: 'a + Copy> Container<C, T> {
   |      ^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:64:9
   |
64 |         self.vec.chunks(self.chunk_size).map(|c| c.into())
   |         ^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that return value is valid for the call
  --> src/main.rs:63:42
   |
63 |     pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> {
   |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Edit: this is the conflicting function. I'm trying to devise a way forward but I still don't understand where the problem lies exactly. The error message doesn't seem very clear to me.

impl<'a, C: Chunkable<T> + From<&'a [T]>, T: 'a + Copy> Container<C, T> {
    pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> {
        self.vec.chunks(self.chunk_size).map(|c| c.into())
    }
}

Haven’t read the whole code yet, but

  // FIXME: lifetime problems
  impl<'a, C: Chunkable<T> + From<&'a [T]>, T: 'a + Copy> Container<C, T> {
      // iterate over the stored components, returning the original type.
-     pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> {
+     pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> + 'a {
          self.vec.chunks(self.chunk_size).map(|c| c.into())
      }
  }

seems to help.

1 Like

You should probably put most bounds on the method itself

impl<C: Chunkable<T>, T> Container<C, T> {
    // iterate over the stored components, returning the original type.
    pub fn chunks_iter_type<'a>(&'a self) -> impl DoubleEndedIterator<Item = C> + 'a
    where
        C: From<&'a [T]>,
    {
        self.vec.chunks(self.chunk_size).map(|c| c.into())
    }
}

This also avoids the need for T: 'a
(in the method, the compiler infers this bound implicitly due to T’s appearance in the type of the self: &'a Container<C, T> argument).

1 Like

Oh wow! That seems to do the trick. Thank you!

Ideally that could have been suggested by the compiler, I wonder if there's already an issue for this but I don't even know how to search for it..

Do you know where I can learn more about this kind of thing?

You are blowing my mind. That's awesome. I feel I have more resources now to keep playing with bounds and lifetimes. Thank you

In summary, now re-reading the compiler error message, I think I understand now that what's implying is that the returned type has an implicit and conflicting static lifetime bound, and I should try to bound it to 'a.

note: but, the lifetime must be valid for the static lifetime...
note: ...so that return value is valid for the call
  --> src/main.rs:63:42
   |
63 |     pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> {
   |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 Likes

Note that

    pub fn chunks_iter(&self) -> impl DoubleEndedIterator<Item = &[T]> {
        self.vec.chunks(self.chunk_size)
    }

works because … well … let’s first get rid of lifetime elision, and look at it again

    pub fn chunks_iter<'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a [T]> {
        self.vec.chunks(self.chunk_size)
    }

now, the impl DoubleEndedIterator<Item = &'a [T]> mentions the lifetime 'a, and this enables it to be able to capture references (or other types) with lifetime 'a as per the rules of how impl Trait in return types works.

(The link is into the RFC introducing the feature because it’s actually not really well-documented otherwise, as far as I’m aware.)

In argument position, the type fulfilling an impl Trait is free to reference any types or lifetimes whatsoever. […]

For return position, things are more nuanced.

This RFC proposes that all type parameters are considered in scope for impl Trait in return position […]. The lifetimes in scope include only those mentioned "explicitly" in a bound on the impl Trait.

Adding the + 'a helps because it (syntactically) “mentions” the lifetime 'a.

1 Like

Applying this knowledge back to

pub fn chunks_iter_type(&'a self) -> impl DoubleEndedIterator<Item = C> {
    self.vec.chunks(self.chunk_size).map(|c| c.into())
}

the return type is the type Map<std::slice::Chunks<'a, T>, {closure type}> where {closure type} stands for the unnameable type of the FnMut(&'a [T]) -> C closure “|c| c.into()”. This type “references/mentions” the lifetime 'a. The “types / lifetimes in scope” in the description in that RFC talks about the question which of the generic type and lifetime arguments can be referenced/mentioned in the actual return type.

I’m not sure if the type of that FnMut(&'a [T]) -> C closure itself also references/mention the lifetime 'a, it doesn’t really matter in this case anyways.

In-fact, I guess the compiler is to be smart about this 'a in Map<std::slice::Chunks<'a, T>, {closure type}> and tries to not use 'a. See, the lifetime is actually some lifetime parameter 'b from the slice::chunks(self: &'b [T], chunk_size: usize) -> Chunks<'b, T> call, so the return type is Map<std::slice::Chunks<'b, T>, {closure type}>.

Rustc notices that

  • this lifetime 'b cannot outlive 'a, because the argument passed to slice::chunks is a re-borrow of self: &'a Container<C, T> (and you cannot reborrow this reference for longer than 'a)
  • the lifetime 'b is mentioned in the return type, but that return type is not allowed to mention any lifetimes at all (besides 'static).
1 Like

You've given me more than I could ever ask for. Bravo! ... In fact I'm gonna have to reread the last post several times to fully grasp it because it goes a little over my head.

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.