NLL 是 Rust 2018 之后借用检查的基础:借用不再「到块结束」,而是「到最后一次使用」。这篇用几个对比实验验证它做了什么、没做什么:控制流里的借用、两阶段借用、Drop 为什么会让借用变长,最后直接看 rustc 内部保存的区域信息。
源码:
1 | fn main() {
2 | let mut v = vec![1, 2, 3];
3 | let first = &v[0];
4 | println!("first = {first}"); // first 的最后一次使用
5 | v.push(4); // 旧的「按作用域」规则会拒绝这一行;NLL 允许
6 | println!("{:?}", v);
7 | let r = &mut v;
8 | r.push(5);
9 | v.push(6); // r 已经不再使用
10 | println!("{:?}", v);
11 | }
编译:成功
运行(退出码 0):
first = 1
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6] 源码:
1 | fn main() {
2 | let mut s = String::from("a");
3 | let r = &s;
4 | let flag = std::env::args().count() > 5;
5 | if flag { println!("{r}"); } // 只有这条路径用 r
6 | else { s.push('b'); } // 另一条路径里 r 已经死了
7 | println!("{s}");
8 | }
编译:成功
运行(退出码 0):
ab 源码:
1 | fn main() {
2 | let mut v = vec![1, 2, 3];
3 | v.push(v.len());
4 | println!("{:?}", v);
5 | }
编译:成功
运行(退出码 0):
[1, 2, 3, 3]
源码:
1 | fn main() {
2 | let mut v = vec![1, 2, 3];
3 | let r = &mut v;
4 | r.push(v.len()); // 先取走了 &mut,再读 v:冲突
5 | }
编译:失败
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
--> <实验目录>/src/u22d.rs:4:12
|
3 | let r = &mut v;
| ------ mutable borrow occurs here
4 | r.push(v.len()); // 先取走了 &mut,再读 v:冲突
| ---- ^ immutable borrow occurs here
| |
| mutable borrow later used by call
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0502`. 源码:
1 | struct Guard<'a>(&'a mut Vec<i32>);
2 | impl Drop for Guard<'_> { fn drop(&mut self) { println!("Guard 释放,vec 长度 {}", self.0.len()); } }
3 | fn main() {
4 | let mut v = vec![1];
5 | let g = Guard(&mut v);
6 | v.push(2);
7 | }
编译:失败
error[E0499]: cannot borrow `v` as mutable more than once at a time
--> <实验目录>/src/u22e.rs:6:5
|
5 | let g = Guard(&mut v);
| ------ first mutable borrow occurs here
6 | v.push(2);
| ^ second mutable borrow occurs here
7 | }
| - first borrow might be used here, when `g` is dropped and runs the `Drop` code for type `Guard`
warning: unused variable: `g`
--> <实验目录>/src/u22e.rs:5:9
|
5 | let g = Guard(&mut v);
| ^ help: if this is intentional, prefix it with an underscore: `_g`
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
error: aborting due to 1 previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0499`.
源码:
1 | struct Guard<'a>(&'a mut Vec<i32>);
2 | impl Drop for Guard<'_> { fn drop(&mut self) { println!("Guard 释放,vec 长度 {}", self.0.len()); } }
3 | fn main() {
4 | let mut v = vec![1];
5 | { let _g = Guard(&mut v); } // 缩小 Guard 的作用域
6 | v.push(2);
7 | println!("{:?}", v);
8 | }
编译:成功
运行(退出码 0):
Guard 释放,vec 长度 1
[1, 2]Drop 的规则见Drop Trait 与资源释放。
生成的 NLL 相关 MIR 转储文件: ['u22g.main.-------.nll.0.mir', 'u22g.main.-------.nll.0.regioncx.all.dot', 'u22g.main.-------.nll.0.regioncx.scc.dot'] (共 3 个)
NLL 区域信息(借用区域 '?N 覆盖的 MIR 位置)节选:
| '?0 | Global | ['?0, '?1]
| '?1 | Local | ['?1]
| '?0 | U0 | {bb0[0..=3], bb1[0..=2], bb2[0..=1], bb3[0..=6], bb4[0..=16], bb5[0..=12], bb6[0..=4], bb7[0..=9], bb8[0..=5], bb9[0..=1], bb10[0], bb11[
| '?1 | U0 | {bb0[0..=3], bb1[0..=2], bb2[0..=1], bb3[0..=6], bb4[0..=16], bb5[0..=12], bb6[0..=4], bb7[0..=9], bb8[0..=5], bb9[0..=1], bb10[0], bb11[
| '?2 | U0 | {bb3[5..=6], bb4[0..=16], bb5[0..=12], bb6[0..=4]}
| '?3 | U0 | {bb4[1..=16], bb5[0..=12], bb6[0..=4]}
| '?4 | U0 | {bb4[8..=16], bb5[0..=12], bb6[0..=4]}
| '?5 | U0 | {bb4[15..=16], bb5[0..=12], bb6[0..=4]}MIR 是什么见MIR 是什么。-Z 选项靠 RUSTC_BOOTSTRAP=1 在稳定版上打开,仅用于观察。