[译] Rust - None Lexical Lifetimes (NLL) 使用指南
原文地址 https://santiagopastorino.com/how-to-use-rust-non-lexical-lifetimes-on-nightly/
原文作者: Santiago Pastorino
转载请留下本文地址 https://zhuanlan.zhihu.com/p/32855335
Niko Matsakis, Paul Faria 和我在 impl period 期间致力于开发None Lexical Lifetimes (暂译为非词法作用域生命周期,下文统称为 NLL)。 代码目前已经合并到 master 分支,你现在可以在 nightly 中体验。
本文将通过几个例子简单直观地展现 NLL 的使用方法。这里我不会涉及 NLL 背后的设计逻辑,如果对设计细节感兴趣,我推荐你进一步阅读 RFC,你想知道的都在里面了。我想你还可以尝试在 pull requests 贡献代码。
话不多说,我们直接上代码。
当然在这之前,你要先确保机器上安装了最新的 nightly 版本 —— 打开命令行运行 rustup install nightly
。
我们先看这个简单的例子,它在目前的作用域生命周期(scope based lifetimes)规则下不能编译。
#![allow(unused_variables)]
fn main() {
let mut x = 22;
let p = &mut x; // mutable borrow
println!("{}", x); // later used
}
这是因为 x 被可变借用,并且在作用域结束前再次被使用。
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
--> src/main.rs:8:20
|
6 | let p = &mut x; // mutable borrow
| - mutable borrow occurs here
7 |
8 | println!("{}", x); // later used
| ^ immutable borrow occurs here
9 | }
| - mutable borrow ends here
error: aborting due to previous error
现在我们在开头加入 #![feature(nll)]
代码开启 NLL 特性。
#![feature(nll)]
#![allow(unused_variables)]
fn main() {
let mut x = 22;
let p = &mut x;
println!("{}", x);
}
这段代码顺利编译,因为编译器知道 x
的可变借用并没有持续到作用域结尾,而是在 x
被再次使用之前就结束了,所以这里不存在冲突。
我们在看一个稍微复杂些的例子。
use std::collections::HashMap;
fn get_default(map: &mut HashMap, key: usize) -> &mut String {
match map.get_mut(&key) {
Some(value) => value,
None => {
map.insert(key, "".to_string());
map.get_mut(&key).unwrap()
}
}
}
fn main() {
let map = &mut HashMap::new();
map.insert(22, format!("Hello, world"));
map.insert(44, format!("Goodbye, world"));
assert_eq!(&*get_default(map, 22), "Hello, world");
assert_eq!(&*get_default(map, 66), "");
}
如果我们直接编译,这段代码也会报错,因为 get_mut
在 match
里借用 map
直到作用域结尾,这段作用域也覆盖到 None
分支,而我们在 None
分支里尝试再次可变借用 map
,编译器自然是十分拒绝的。
错误信息是这样的
error[E0499]: cannot borrow `*map` as mutable more than once at a time
--> src/main.rs:7:13
|
4 | match map.get_mut(&key) {
| --- first mutable borrow occurs here
...
7 | map.insert(key, "".to_string());
| ^^^ second mutable borrow occurs here
...
11 | }
| - first borrow ends here
error[E0499]: cannot borrow `*map` as mutable more than once at a time
--> src/main.rs:8:13
|
4 | match map.get_mut(&key) {
| --- first mutable borrow occurs here
...
8 | map.get_mut(&key).unwrap()
| ^^^ second mutable borrow occurs here
...
11 | }
| - first borrow ends here
error: aborting due to 2 previous errors
不开启 NLL 的情况下,我们有一种丑陋的技巧可以让它通过编译。
fn get_default(map: &mut HashMap, key: usize) -> &mut String {
match map.get_mut(&key) {
Some(value) => return value,
None => {
}
}
map.insert(key, "".to_string());
map.get_mut(&key).unwrap()
}
这段代码虽然能编译,代价是我们必须使用这种令人难受的写法。
如果我们在原先的例子中加入 #![feature(nll)]
开启 NLL ...
#![feature(nll)]
use std::collections::HashMap;
fn get_default(map: &mut HashMap, key: usize) -> &mut String {
match map.get_mut(&key) {
Some(value) => value,
None => {
map.insert(key, "".to_string());
map.get_mut(&key).unwrap()
}
}
}
fn main() {
let map = &mut HashMap::new();
map.insert(22, format!("Hello, world"));
map.insert(44, format!("Goodbye, world"));
assert_eq!(&*get_default(map, 22), "Hello, world");
assert_eq!(&*get_default(map, 66), "");
}
这段代码完美通过编译,并且再也不用写那种令人智熄的代码。
另一个有趣的东西是用来展示借用错误的叫做 three point error
的机制。目前为了开启它你需要显式地给编译器传入 -Znll-dump-cause
参数。
先看这个在 NLL 下产生借用错误的例子。
#![feature(nll)]
#![allow(unused_assignments)]
fn main() {
let mut x = 22;
let p = &x;
x = 33;
println!("{}", p);
}
编译时传入 nll-dump-cause
,three point errors 会产生下面这样的的错误信息
$ rustc -Znll-dump-cause main.rs
error[E0506]: cannot assign to `x` because it is borrowed
--> src/main.rs:9:5
|
7 | let p = &x;
| -- borrow of `x` occurs here
8 |
9 | x = 33;
| ^^^^^^ assignment to borrowed `x` occurs here
10 |
11 | println!("{}", p);
| - borrow later used here
error: aborting due to previous error
错误信息指出了起始借用,借用赋值和使用借用的位置,这应该可以给你提供对于问题清晰的思路。
这个机制目前还有性能上的问题,因此我们暂时需要自己手动开启。我们打算在解决性能问题后就将它设为默认。我和 Niko 已经着手,请拭目以待 :)。
我和你们一样非常兴奋于 NLL 最终落地,去尝试使用 NLL 写些代码,然后反馈你遇到的 bug 吧。虽然 NLL 已知存在一些问题需要修复,但是我们知道这都会慢慢完善起来的。
最后,我想说些无关紧要的话,我想分享一下我在 impl period 中的参与这个项目的体验。
首先,我从未想过我竟然能够有机会直接与 Niko 共事,更别说这还是我的第一个 Rust 项目。
Niko 是个令人敬仰的专家 (professional),更重要的是他非常友好,容易亲近。我们在 Gitter 上交谈了很久,打过 call,然后我们还一起在 Rust Belt Rust 工作过 3 天。
我还与其他开发者花了不少时间讨论想法,比如说 Paul, 我非常感谢他的支持。