Rust具有与非词汇生存期相关的RFC,该RFC已被批准长时间使用该语言来实现。最近,Rust对此功能的支持已改进很多,并被认为是完整的。
我的问题是:非词汇寿命到底是什么?
Answers:
通过了解什么是词法生存期,最容易理解什么是非词法生存期。在存在非词汇生存期之前的Rust版本中,此代码将失败:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Rust编译器认为scores
该score
变量是借来的,因此它不允许进一步突变scores
:
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let score = &scores[0];
| ------ immutable borrow occurs here
4 | scores.push(4);
| ^^^^^^ mutable borrow occurs here
5 | }
| - immutable borrow ends here
然而,人可以平凡看到,这个例子是过于保守:score
是从来没有使用过!问题是scores
by的借用score
是词法的-一直持续到包含它的块的末尾:
fn main() {
let mut scores = vec![1, 2, 3]; //
let score = &scores[0]; //
scores.push(4); //
// <-- score stops borrowing here
}
非词汇生存期可以通过增强编译器以了解这一详细程度来解决此问题。现在,编译器可以更准确地判断何时需要借用,并且此代码将进行编译。
非词汇生命周期的一个奇妙之处在于,一旦启用,就永远不会有人想到它们。它只会变成“ Rust所做的事情”,并且一切都会(希望)能正常工作。
Rust旨在仅允许编译已知安全的程序。但是,不可能完全只允许安全程序而拒绝不安全程序。为此,Rust犯了保守主义的错误:拒绝了某些安全程序。词汇寿命就是一个例子。
词法生存期在编译器中更容易实现,因为对块的了解是“琐碎的”,而对数据流的了解则较少。需要重写编译器以引入和利用“中级中间表示”(MIR)。然后,必须重写借位检查器(又名“借款人”)以使用MIR而不是抽象语法树(AST)。然后,必须完善借阅检查器的规则以更细化。
词法生存期并不总是会妨碍程序员,而且有很多方法可以解决词法生存期,即使它们很烦人。在许多情况下,这涉及添加额外的花括号或布尔值。这使得Rust 1.0可以发布,并且在实现非词性生存期之前可以使用很多年。
有趣的是,由于词汇的生命周期,某些良好的模式得以发展。对我来说,entry
最主要的例子就是模式。此代码在非词法生存期之前失败并使用它进行编译:
fn example(mut map: HashMap<i32, i32>, key: i32) {
match map.get_mut(&key) {
Some(value) => *value += 1,
None => {
map.insert(key, 1);
}
}
}
但是,此代码效率很低,因为它两次计算密钥的哈希值。由于词法生存期而创建的解决方案更短,更有效:
fn example(mut map: HashMap<i32, i32>, key: i32) {
*map.entry(key).or_insert(0) += 1;
}
值的生存期是该值停留在特定内存地址上的时间跨度(有关详细说明,请参阅为什么我不能在同一结构中存储值和对该值的引用?)。称为非词汇生存期的功能不会更改任何值的生存期,因此它不能使生存期成为非词汇生存期。它只会使对这些值的借用的跟踪和检查更加精确。
该功能更准确的名称可能是“非词汇借用”。一些编译器开发人员引用了底层的“基于MIR的rowck”。
从本质上讲,非词汇生存期从来都不打算成为“面向用户”的功能。由于我们在缺席的情况下剪纸少,因此它们在我们的脑海中变得越来越大。他们的名字主要用于内部开发目的,而将其更改为用于营销目的从来都不是优先事项。
在Rust 1.31(于2018-12-06发布)中,您需要在Cargo.toml中选择加入Rust 2018版本:
[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"
从Rust 1.36开始,Rust 2015版本还启用了非词汇生存期。
当前非词汇生存期的实现方式是“迁移模式”。如果NLL借阅检查器通过,则编译将继续。如果不是,则调用先前的借阅检查器。如果旧的借阅检查器允许该代码,则会显示警告,通知您您的代码很可能在Rust的未来版本中中断,应进行更新。
在每晚版本的Rust中,您可以通过功能标志选择加入强制性破损:
#![feature(nll)]
您甚至可以使用编译器标志选择加入NLL的实验版本-Z polonius
。