Can anybody explain why mutable version doesn't compile, while immutable version does?

I have iterator implementations, where the first one returns immutable reference to the buffer and the second one returns mutable reference to the buffer.
When I compile the code, the mutable version fails to compile with "error: lifetime may not live long enough".

I understand the possible danger of this code(let's suppose that there was a bug so same index's mutable reference has been returned twice), but I can't understand how the compiler figures lifetime in this code.

Anybody willing to explain how the compiler rejects the second version, while accepting the first version?

pub struct Iter<'a, T> {
    buffer: &'a [T],
    position: usize,
    remaining: usize,
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.remaining == 0 {
            return None;
        }

        let len = self.buffer.len();
        let item = &self.buffer[self.position];
        self.position = (self.position + 1) % len;
        self.remaining -= 1;
        Some(item)
    }
}

pub struct IterMut<'a, T> {
    buffer: &'a mut [T],
    position: usize,
    remaining: usize,
}

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.remaining == 0 {
            return None;
        }

        let len = self.buffer.len();
        let item = &mut self.buffer[self.position];
        self.position = (self.position + 1) % len;
        self.remaining -= 1;
        Some(item)
    }
}

(Playground)

Here,

impl<'a, T> Iterator for IterMut<'a, T> {
    fn next(&mut self) -> Option<Self::Item> {

The type of &mut self is &mut IterMut<'a, T> and the 'a can't change. You can't get a &'long mut T from a &'short mut &'long mut T.

But you can get a &'long T out of a &'short mut &'long T. Shared references are Copy, so you can just copy it out. So your shared iterator works.


It's still possible to write your IterMut in safe code, which I'll illustrate shortly.

3 Likes

There's an even more fundamental problem by the way -- &mut indicates exclusivity. So it's not possible to write your IterMut using &mut [T] in such a way that you hold on to the entire slice while handing out &mut T to individual elements in the slice -- that would violate exclusivity! There would be access through your slice and there would be access through the returned &mut T at the same time. That's UB.

You need some way to split the borrow, where you turn the &mut [T] into non-overlapping parts &mut T and &mut [T]. There's methods to do borrow splitting on slices directly, or you can use pattern matching to split the first element, say.

        if let Some((first, rest)) = match self.buffer {
            [first, rest @ ..] => Some((first, rest)),
            _ => None,
        } {
            ...
        }

However if you try these, you'll find out you run into the same restriction above -- you can't get a &'long mut through a &'short mut.

A safe way to address this portion of the problem is to remove the &'long mut [T] from behind the &'short mut self. But you can just do this either:

        let slice = self.buffer;
error[E0507]: cannot move out of `self.buffer` which is behind a mutable reference
  --> src/lib.rs:12:21
   |
12 |         let slice = self.buffer;
   |                     ^^^^^^^^^^^ move occurs because `self.buffer` has type `&mut [T]`, which does not implement the `Copy` trait

You have to leave something in it's place.

Fortunately, &mut [] -- the empty exclusive slice (for any contained T) -- is special-cased. Because it doesn't actually alias any memory -- it points at 0 bytes -- you can conjure them out of nowhere, with any lifetime, despite being an exclusive borrow.

There's even a Default implementation for them.

So putting those together, you can do this:

    fn next(&mut self) -> Option<Self::Item> {
        // Get the inner slice out from under `&'short mut self`
        let slice = std::mem::take(&mut self.buffer);

        // Split the borrow
        let (first, rest) = slice.split_first_mut()?;

        // Put the rest of the slice back
        self.buffer = rest;

        // And return
        self.position += 1; // If you need this
        self.remaining -= 1; // This is just self.buffer.len() now, you don't need it
        Some(first)
    }
7 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.