rustlifetime

Mutable borrow in previous iteration, or maybe a lifetime issue?


I've got code that will not compile, and I do not understand why. Here is a simplified, contrived version of it:

struct Thing<'a> {
    slice: &'a [u8],
}

struct ThingGetter {
    buffer: [u8; 10],
    index: usize,
}

impl ThingGetter {
    pub fn next_thing(&mut self) -> Option<Thing> {
        self.index += 1;
        if self.index == 42 {
            return None;
        } else {
            let thing = Thing {
                slice: &self.buffer[..],
            };
            return Some(thing);
        }
    }
}

struct Wrapper {
    getter: ThingGetter,
}

impl Wrapper {
    pub fn get_thing(&mut self) -> Option<Thing> {
        loop {
            if let Some(thing) = self.getter.next_thing() {
                return Some(thing);
            } else {
                continue;
            }
        }
    }
}

It fails with:

error[E0499]: cannot borrow `self.getter` as mutable more than once at a time
  --> src/thing.rs:31:34
   |
29 |     pub fn get_thing(&mut self) -> Option<Thing> {
   |                      - let's call the lifetime of this reference `'1`
30 |         loop {
31 |             if let Some(thing) = self.getter.next_thing() {
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^ `self.getter` was mutably borrowed here in the previous iteration of the loop
32 |                 return Some(thing);
   |                        ----------- returning this value requires that `self.getter` is borrowed for `'1`

I have slogged through a ton of Stack Overflow answers pertaining to multiple mutable borrows and to lifetimes, but I'm just not seeing the answer.

If the continue; line is changed to return None; it compiles fine.

I understand that thing depends on getter, which is borrowed mutably. What I don't see is why this code thinks that getter is getting borrowed twice, once in the previous iteration. The borrow from the previous iteration is no longer accessible once you call if let Some(thing) = ... again.

Or maybe this is a lifetime issue? I can't tell from the compiler message.

Here's a link to the playground.


Solution

  • I'm quite certain you encountered the largest known false positive of the current borrow checker.

    The new experimental borrow checker, called polonius, successfully confirms the validity of your code. It isn't stable yet, though.

    RUSTFLAGS="-Zpolonius" cargo +nightly build
    

    There are many similar examples for it, but it all boils down to the problem described here.

    In most cases, it's possible to work around this problem. In some rare cases, however, it isn't possible to solve this properly without unsafe code. Luckily, the polonius-the-crab crate provides a sound encapsulation for this small unsafe code. With its help, your problem could be solved like this:

    use ::polonius_the_crab::prelude::*;
    
    struct Thing<'a> {
        slice: &'a [u8],
    }
    
    struct ThingGetter {
        buffer: [u8; 10],
        index: usize,
    }
    
    impl ThingGetter {
        pub fn next_thing(&mut self) -> Option<Thing> {
            self.index += 1;
            if self.index == 42 {
                return None;
            } else {
                let thing = Thing {
                    slice: &self.buffer[..],
                };
                return Some(thing);
            }
        }
    }
    
    struct Wrapper {
        getter: ThingGetter,
    }
    
    impl Wrapper {
        pub fn get_thing(&mut self) -> Option<Thing> {
            let mut this = self;
            polonius_loop!(|this| -> Option<Thing<'polonius>> {
                if let Some(thing) = this.getter.next_thing() {
                    polonius_return!(Some(thing));
                } else {
                    polonius_continue!();
                }
            })
        }
    }