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
不再使用,那么第一个代码片段应该是安全的。编译器能否更加智能化,使我们不需要像第二个代码片段那样明确地将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;
r;
尽管在
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
以下代码是无效的,因为在
r
被赋值之后,在使用
r
之前,
x
已经被改变了:
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;
x = 2;
r;