Life before NLL
Before discussing about non-lexical lifetimes (NLL), let's first discuss the "ordinary" lifetimes. In older Rust before NLL is introduced, the code below won't compile because r is still in scope while x is mutated in row 3.
let mut x = 1;
let mut r = &x;
x = 2; // Compile error
To fix this, we need to explicitly make r out of scope before x is mutated:
let mut x = 1;
{
let mut r = &x;
}
x = 2;
At this point you might think: If after the line of x = 2, r is not used anymore, the first snippet should be safe. Can the compiler be smarter so that we don't need to explicitly make r out of scope like we did in the second snippet?
The answer is yes, and that's when NLL comes in.
Life after NLL
After NLL is introduced in Rust, our life becomes easier. The code below will compile:
let mut x = 1;
let mut r = &x;
x = 2; // Compiles under NLL
But remember, it will compile as long as r is not used after the mutation of x. For example, this won't compile even under NLL:
let mut x = 1;
let mut r = &x;
x = 2; // Compile error: cannot assign to `x` because it is borrowed
r; // borrow later used here
Although the rules of NLL described in RFC 2094 are quite complex, they can be summarized roughly and approximately (in most cases) as:
A program is valid as long as every owned value is not mutated between the assignment of a variable referring to it and the usage of that variable.
The code below is valid because x is mutated before the assignment of r and before the usage of r:
let mut x = 1;
x = 2; // x is mutated
let mut r = &x; // r is assigned here
r; // r is used here
The code below is valid because x is mutated after the assignment of r and after the usage of r:
let mut x = 1;
let mut r = &x; // r is assigned here
r; // r is used here
x = 2; // x is mutated
The code below is NOT valid because x is mutated after the assignment of r and before the usage of r:
let mut x = 1;
let mut r = &x; // r is assigned here
x = 2; // x is mutated
r; // r is used here -> compile error
To your specific program, it's valid because when x is mutated (x = 2), there is no variable referring to x anymore—r is now referring to y because of the previous line (r = &y). Therefore, the rule is still adhered.
let mut x = 1;
let mut r = &x;
r;
let y = 1;
r = &y;
// This mutation of x is seemingly sandwiched between
// the assignment of r above and the usage of r below,
// but it's okay as r is now referring to y and not x
x = 2;
r;