4

This doesn't make much sense because it returns the same thing regardless of the if let, but it is a concise example of the problem that I am running into:

struct Data {
    value: Option<i32>,
}

impl Data {
    fn get(&mut self) -> Option<&mut i32> {
        if let Some(val) = &mut self.value {
            return Some(val);
        }

        return self.value.as_mut();
    }
}

This code produces the error:

error[E0499]: cannot borrow `self.value` as mutable more than once at a time
  --> src/lib.rs:11:16
   |
6  |     fn get(&mut self) -> Option<&mut i32> {
   |            - let's call the lifetime of this reference `'1`
7  |         if let Some(val) = &mut self.value {
   |                            --------------- first mutable borrow occurs here
8  |             return Some(val);
   |                    --------- returning this value requires that `self.value` is borrowed for `'1`
...
11 |         return self.value.as_mut();
   |                ^^^^^^^^^^ second mutable borrow occurs here

I don't understand why this is a second mutable borrow when the first one goes out of scope before the second one will ever occur.

The val variable is not in scope after the if let, so how is this a second borrow? The first borrow should have already been released.

Just to be completely sure, I even surrounded the if let with another block:

{
    if let Some(val) = &mut self.value {
        return Some(val);
    }
}

return self.value.as_mut();

This produced the same error. What is going on here?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • maybe it's more clear with https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=7765d0286fae5d7bcc07cef9ab593884 – Stargateur Jun 02 '19 at 22:14
  • By the way, it's not idiomatic Rust to use explicit `return` at the end of a block. – Shepmaster Jun 03 '19 at 15:32

1 Answers1

9

The lifetime of 'a encompasses the entire function body because the return value needs to borrow self. Hence, the first borrow's scope extends past the if expression and into the entire function body.

Non Lexical Lifetimes were intended to fix this issue by reducing the scope of the first borrow to only include the if expression. You can see this in effect by moving the borrowed value into a local variable (playground):

fn get(&mut self) -> Option<&mut i32> {
    let value = &mut self.value;
    if let Some(val) = value {
        return Some(val);
    }
    return value.as_mut();
}

However, the support for conditionally returning values was removed because it took too much compilation time. This feature is still being worked on and can be enabled with the -Zpolonius flag:
RUSTFLAGS="-Zpolonius" cargo +nightly build

With which, the original code compiles fine.

vallentin
  • 23,478
  • 6
  • 59
  • 81
Technocoder
  • 363
  • 4
  • 9