NLL 之前的生活
在讨论非词汇生命周期 (NLL) 之前,让我们先讨论“普通”生命周期。在引入 NLL 之前的旧版 Rust 中,下面的代码将无法编译,因为 r 仍在作用域内,而 x 在第 3 行发生了变异。
let mut x = 1;
let mut r = &x;
x = 2; // Compile error
要解决这个问题,我们需要在 x 发生突变之前明确地使 r 超出范围:
let mut x = 1;
{
let mut r = &x;
}
x = 2;
此时你可能会想:如果x = 2这行之后,r不再使用,那么第一个sn-p应该是安全的。编译器能否更智能,这样我们就不需要像在第二个 sn-p 中那样显式地使 r 超出范围?
答案是肯定的,那就是 NLL 出现的时候。
NLL 之后的生活
在 Rust 中引入 NLL 后,我们的生活变得更加轻松。下面的代码将编译:
let mut x = 1;
let mut r = &x;
x = 2; // Compiles under NLL
但请记住,只要x 突变后不使用r,它就会编译。例如,即使在 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
虽然RFC 2094中描述的NLL规则相当复杂,但可以粗略地(在大多数情况下)概括为:
只要每个拥有的值在引用它的变量的赋值和该变量的使用之间没有发生突变,程序就是有效的。
下面的代码是有效的,因为x在r的赋值之前在和r的使用之前在之前发生了变异:
let mut x = 1;
x = 2; // x is mutated
let mut r = &x; // r is assigned here
r; // r is used here
下面的代码是有效的,因为x在r的赋值之后和r的使用之后发生了变异:
let mut x = 1;
let mut r = &x; // r is assigned here
r; // r is used here
x = 2; // x is mutated
下面的代码无效,因为x 在r 的赋值之后 和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
对于您的特定程序,它是有效的,因为当x 发生突变时(x = 2),不再有引用x 的变量——r 现在指的是y,因为前一行( r = &y)。因此,仍然遵守规则。
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;