Lifetimes scope and borrowing (newb)

Huh? I guess there is something missing in my understanding of life times. Wwhy does this compile and run:

fn main() {
    let x = "foo";
    let r;
    r = &x;
    println!("x: {}", x);
    println!("r: {}", r);
}

But this does not:

fn main() {
    let r;
    let x = "foo";
    r = &x;
    println!("x: {}", x);
    println!("r: {}", r);
}
error[E0597]: `x` does not live long enough
  --> src/main.rs:5:10
   |
5  |     r = &x;
   |          ^ borrowed value does not live long enough
...
11 | }
   | - `x` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

When exactly does a lifetime end? The only thing that changed was the order of declaration for x and r. I would normally think they are both in scope until the end of the block as indicated by the last curly brace. So there is something else going on here besides scope, methinks.

As the error message notes, values are destroyed in the opposite order from their creation, so x goes out of scope before r.

However, in the new Rust 2018 Edition, values only remained borrowed while a reference is actively used, not while it is in scope, so both of your programs will compile. For more information, see Non-Lexical Lifetimes.

3 Likes

The order of going out of scope makes sense to me, but not when. Why isn't the scope still active further down the block. I was thinking the scope should last until the end of the block but it appears the lifetime expires before the println.

I get the error above in both the Rust Playground and locally. Do I need to update my version? I'm using rustc version 1.30.1

In the old system ("lexical lifetimes"), the thing that is borrowed must outlive the entire scope of the reference that borrows it. In the second program, the referent x goes out of scope while the reference r still exists, so this program is not allowed.

In the new ("non-lexical") system, the referent only needs to be valid until the last use of the reference, so it's fine for x to be destroyed anytime after the final println call.

On the Rust playground, set the "Edition" under "... Advanced options" to 2018: Rust Playground

Locally, upgrade to Rust 1.31 and add edition = "2018" to your Cargo.toml. (It'll be added by default to newly-created Cargo projects.) Or use rustc --edition=2018 if you are invoking rustc yourself.

(In a future version of rustc, all code regardless of edition will use the new borrow checker.)

1 Like

Ah. Makes sense now. But moot seeing that 2018 version changes it. Thanks!

Just asking, NLL only expands what LL could do right? I'm not going to find an example where it compiles in LL, but not in NLL?

Yes, that is correct*

* if there was a bug in the old system that NLL fixes then you will find a case where it compiles in the old cases but not the new, but you would need to go looking for that.

1 Like