Borrow checker and early return

Why it is not compiles?

struct Tmp(Vec<u32>);

impl Tmp {
    fn test(&mut self) -> &mut u32 {
        {
            let opt = self.0.iter_mut().find(|x| **x == 12);
            
            if let Some(val) = opt {
                return val;
            }

            // opt it dropped and reference released
        }
        
        &mut self.0[0]
    }
}

fn main() {}

error:

error[E0499]: cannot borrow `self.0` as mutable more than once at a time
  --> src/main.rs:13:14
   |
6  |             let opt = self.0.iter_mut().find(|x| **x == 12);
   |                       ------ first mutable borrow occurs here
...
13 |         &mut self.0[0]
   |              ^^^^^^ second mutable borrow occurs here
14 |     }
   |     - first borrow ends here
2 Likes

I am not 100% sure: in your inner block code, the return which jumps out of that control block, making the opt ends in function statement block instead. Since opt is a ref to an item in self.0, which is effectively a borrow of self.0, the compiler complains.

This below code will compile:

struct Tmp(Vec<u32>);

impl Tmp {
    fn test(&mut self) -> &mut u32 {
        {
            let pos = self.0.iter_mut().position(|&mut x| x == 12);
            if let Some(index) = pos {
                return &mut self.0[index];
            }
            
        }
        return &mut self.0[0];
    }
}

fn main() {}

It compiles if you comment out return. This suggests the conflict is caused by the return type. It's equivalent to:

fn test<'self>(&'self mut self) -> &'self mut u32 

so I guess the borrow checker does reasoning backwards, that if return val; is borrowed for the life for 'self lifetime, then opt is borrowed for entire 'self too, and self.0 is borrowed for the entire function too.

1 Like

It seems like the kind of case that #![feature(nll)] might allow, but that doesn't work either.

This scenario was one of the original motivations for NLL but it was moved into Polonius (NLL v2) instead, I believe.