了解什么是非词汇生命周期最简单的方法是了解什么是词汇 生命周期。在 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
对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)。然后,借用检查器(也称为“borrowck”)必须被重写以使用MIR而不是抽象语法树(AST)。接着,借用检查器的规则必须被细化。
词法生命周期不总是妨碍程序员,当它们确实妨碍时有许多解决方法,即使它们很烦人。在许多情况下,这涉及添加额外的花括号或布尔值。这使得Rust 1.0可以发布并且在非词法生命周期实现之前有多年的实用性。
有趣的是,由于词法生命周期,出现了某些好的模式。对我来说,最好的例子就是条目模式。这段代码在非词法生命周期之前会失败,在其实现后编译通过:
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的borrowck。
非词法生命周期从来没有被打算成为一个“面向用户”的特性。它们在我们的心目中大多是因为缺少它们而受到伤害。它们的名字主要是为了内部开发目的而设计的,并且出于营销目的而更改它们的名称从来不是一个优先事项。
在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)]
您甚至可以使用编译器标志-Z Polonius
选择加入非词法生命周期(NLL)实验版本。
push
)添加元素可能会强制重新分配,从而更改其地址,但不会丢失其绑定。对于这位新手来说,生命周期系统似乎都围绕着绑定:所有者、借用者和观察者(也被称为共享)。想想看,在Rust中的观察者模式可能非常简单。 - George