[Pre-RFC] Demoting Lifetimes at Return

This is a discussion about an RFC for improving the lifetime checks for values which may be returned.

To demonstrate here is are two logically identical functions:

//This code fails to compile
fn foo<'a>(values: &'a mut Vec<i32>) -> &'a mut i32 {
  let v = &mut values[0];

  if /*check involving v*/ { v }
  else {
    //Errors because the lifetime of `v` is `'a` since
    //  v may be returned from the function.
    &mut values[1]
  }
}

//This code compiles successfully
fn bar<'a>(values: &'a mut Vec<i32>) -> &'a mut i32 {
  let v = &mut values[0];

  if /*check involving v*/ { &mut values[0] }
  else { &mut values[1] }
}

As pointed out in the code comments only bar will compile, even though the logic of both functions is the same. I believe it is confusing and frustrating to rustaceans old and new when you write code which is logically correct but fails to compile because the borrow checker is being overly cautious.
This situation becomes a greater issue when getting the borrow for v is also expensive to do (i.e. not just indexing a slice) as the solution proposed in bar doubles the time spent calculating the same value for v.

I believe that when return statements are involved like this, the borrow checker should demote the lifetime of the borrow (v in the example) to be until its last appearance in the function, improving Rusts ergonomics in the same way Non-Lexical Lifetimes have.

I believe this is one of the thing Polonius (the 3rd gen borrow checker) is suppose to fix.

The code compiles fine with -Z polonius, but Polonius isn't done so that can't be taken as a guarantee.

9 Likes