Lifetimes for immutable and mutable versions

In a bid to understand lifetimes better I tried implementing a immutable and mutable iterator around a collection. Of course, the code is only for understanding purposes. But the mutable iterator implementation doesn't compile while the similar immutable version does. I know how to fix it, in the sense that I serendipitously arrived at the solution. But I don't understand how that works. I am pasting that too so someone could explain to me (a) why the first one does not and (b) why the second one does.


struct MyIterator<'a, T>
{
    slice : &'a [T]
}

impl<'a, T> Iterator for MyIterator<'a, T>
{
    type Item = &'a T;
    
    fn next(&mut self) -> Option<Self::Item>
    {
        let (element, rest) = self.slice.split_first()?;
        self.slice = rest;
        Some(element)
    }
}

struct MyMutableIterator<'a, T>
{
    slice : &'a mut [T]
}

impl<'a, T> Iterator for MyMutableIterator<'a, T>
{
    type Item = &'a mut T;
    
    fn next(&mut self) -> Option<Self::Item>
    {
        let (element, rest) = self.slice.split_first_mut()?;
        self.slice = rest;
        Some(element)
    }
}

I get the following the compilation error:

error: lifetime may not live long enough
  --> src/main.rs:31:9
   |
23 | impl<'a, T> Iterator for MyMutableIterator<'a, T>
   |      -- lifetime `'a` defined here
...
27 |     fn next(&mut self) -> Option<Self::Item>
   |             - let's call the lifetime of this reference `'1`
...
31 |         Some(element)
   |         ^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`

Here is the version of the mutable iterator that works:

impl<'a, T> Iterator for MyMutableIterator<'a, T>
{
    type Item = &'a mut T;
    
    fn next(&mut self) -> Option<Self::Item>
    {
        let slice = &mut self.slice;
        let slice = std::mem::replace(slice, &mut []);
        let (element, rest) = slice.split_first_mut()?;
        self.slice = rest;
        Some(element)
    }
}

The above version compiles. But I can't explain why?

Thanks in advance and Merry Xmas

FYI, there's a &mut [T] impl, so you can use std::mem::take here, too.


The key to understanding the difference here is the following information: if you have a &'a mut &'b mut S reference, dereferencing / reborrowing will only allow you to obtain a shorter lived &'a mut S, and not a &'b mut S. The analogous situation for shared / immutable references &'a mut &'b S does give the longer-lived &'b S (mainly because &S is Copy). When you want to write back the updated reference &'a mut [T] back to the target of the &'a mut &'b mut [T] the lifetimes will mismatch of course. Now, mem::replace can get the full &'b mut [T] with its full lifetime from behind a mutable reference. More generally, mem::replace is the go-to solution to achieve analogous results to what simple dereferencing can do for Copy types, so it is in principle not surprising that changing from &S to &mut S, i. e. from a Copy type to a non-Copy one, requires mem::replace (or similar helper functions) to be used; the only unusual bit here is that usually you do that to fix a value cannot be moved kind of error, not a lifetime error, which is what you got here because the compiler tried to help by allowing the (shorter lived) &'a mut [T] to be easily obtained in the first place via (implicit) re-borrowing.

In the code, the types involved are not literally references to references, but for this discussion the layer of struct around the inner reference is irrelevant.

3 Likes

Here is a presentation of how to build build an iterator for MyCollection That includes contributions from people in this user forum.

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.