【发布时间】:2020-01-06 10:15:18
【问题描述】:
Rust 有一个RFC 与非词汇生命周期相关,has been approved 将在该语言中长期实现。 Recently,Rust 对这个特性的支持提升了很多,算是完善了。
我的问题是:究竟什么是非词汇生命周期?
【问题讨论】:
标签: rust terminology lifetime lifetime-scoping
Rust 有一个RFC 与非词汇生命周期相关,has been approved 将在该语言中长期实现。 Recently,Rust 对这个特性的支持提升了很多,算是完善了。
我的问题是:究竟什么是非词汇生命周期?
【问题讨论】:
标签: rust terminology lifetime lifetime-scoping
通过了解lexical 生命周期是什么,最容易理解非词汇生命周期是什么。在存在非词法生命周期之前的 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从未使用过!问题是score借用scores是lexical——它一直持续到包含它的块的末尾:
fn main() {
let mut scores = vec![1, 2, 3]; //
let score = &scores[0]; //
scores.push(4); //
// <-- score stops borrowing here
}
非词法生命周期通过增强编译器以了解此详细程度来解决此问题。编译器现在可以更准确地判断何时需要借用,并且此代码将编译。
非词法生命周期的美妙之处在于,一旦启用,没有人会想到它们。它将简单地成为“Rust 所做的事情”,并且事情会(希望)正常工作。
Rust 只允许编译已知安全的程序。但是,it is impossible 完全允许仅 安全程序并拒绝不安全程序。为此,Rust 在保守方面犯了错误:一些安全程序被拒绝。词汇生命周期就是一个例子。
词法生命周期在编译器中更容易实现,因为块的知识是“微不足道的”,而数据流的知识则不那么重要。编译器必须是rewritten to introduce and make use of a "mid-level intermediate representation" (MIR)。然后必须重写借用检查器(又名“borrowck”)以使用 MIR 而不是抽象语法树(AST)。然后必须细化借用检查器的规则以使其更细。
词汇生命周期并不总是妨碍程序员,有很多方法可以绕过它们,即使它们很烦人。在许多情况下,这涉及添加额外的花括号或布尔值。这使得 Rust 1.0 在实现非词法生命周期之前可以发布和使用很多年。
有趣的是,某些 good 模式是由于词汇生命周期而开发的。对我来说最好的例子是the entry pattern。此代码在非词法生命周期之前失败并使用它进行编译:
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;
}
值的生命周期是值停留在特定内存地址的时间跨度(请参阅Why can't I store a value and a reference to that value in the same struct? 以获得更长的解释)。被称为非词法生命周期的特性不会更改任何值的生命周期,因此它不能使生命周期成为非词法的。它只会使对这些值的借用的跟踪和检查更加精确。
该功能的更准确名称可能是“非词法借用”。一些编译器开发人员参考了底层的“基于 MIR 的借用”。
非词法生命周期从未打算成为“面向用户”的功能,本身。由于我们从他们的缺席中得到的小剪纸,它们在我们的脑海中大部分都变大了。他们的名字主要用于内部开发目的,而为了营销目的而更改它从来都不是优先事项。
在 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 中的观察者模式可能非常简单。