Non-lexical lifetimes

Minimum Rust version: 1.31 for 2018 edition

Minimum Rust version: 1.36 for 2015 edition

The borrow checker has been enhanced to accept more code, via a mechanismcalled "non-lexical lifetimes." Consider this example:

  1. fn main() {
  2. let mut x = 5;
  3. let y = &x;
  4. let z = &mut x;
  5. }

In older Rust, this is a compile-time error:

  1. error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  2. --> src/main.rs:5:18
  3. |
  4. 4 | let y = &x;
  5. | - immutable borrow occurs here
  6. 5 | let z = &mut x;
  7. | ^ mutable borrow occurs here
  8. 6 | }
  9. | - immutable borrow ends here

This is because lifetimes follow "lexical scope"; that is, the borrow from y isconsidered to be held until y goes out of scope at the end of main, even thoughwe never use y again. This code is fine, but the borrow checker could not handle it.

Today, this code will compile just fine.

Better errors

What if we did use y, like this?

  1. fn main() {
  2. let mut x = 5;
  3. let y = &x;
  4. let z = &mut x;
  5. println!("y: {}", y);
  6. }

Here's the error:

  1. error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  2. --> src/main.rs:5:18
  3. |
  4. 4 | let y = &x;
  5. | - immutable borrow occurs here
  6. 5 | let z = &mut x;
  7. | ^ mutable borrow occurs here
  8. ...
  9. 8 | }
  10. | - immutable borrow ends here

With non-lexical lifetimes, the error changes slightly:

  1. error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  2. --> src/main.rs:5:13
  3. |
  4. 4 | let y = &x;
  5. | -- immutable borrow occurs here
  6. 5 | let z = &mut x;
  7. | ^^^^^^ mutable borrow occurs here
  8. 6 |
  9. 7 | println!("y: {}", y);
  10. | - borrow later used here

Instead of pointing to where y goes out of scope, it shows you wherethe conflicting borrow occurs. This makes these sorts of errors far easier to debug.