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.