什么是非词汇生命?

Sta*_*eur 63 lifetime rust lifetime-scoping

Rust有一个与非词汇生命周期相关的RFC,已被批准在该语言中实现了很长时间.最近,Rust对此功能的支持有了很大改进,并且被认为是完整的.

我的问题是:非词汇生命究竟是什么?

She*_*ter 92

通过理解词汇生命周期,最容易理解非词汇生命周期是什么.在存在非词法生命周期之前的Rust版本中,此代码将失败:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.push(4);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

然而,人可以平凡看到,这个例子是过于保守:score从来没有使用过!的问题是,借scoresscore词汇 -它持续直到其被包含在所述块的端部:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.push(4);                 //
                                    // <-- score stops borrowing here
}
Run Code Online (Sandbox Code Playgroud)

非词汇生命周期通过增强编译器来理解这种细节水平来解决这个问题.编译器现在可以更准确地判断何时需要借用并且此代码将被编译.

关于非词汇生命周期的一个奇妙的事情是,一旦启用,没有人会想到它们.它将简单地变成"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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,此代码效率低,因为它会计算密钥的哈希值两次.由于词汇生命周期而创建的解决方案更短且更有效:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}
Run Code Online (Sandbox Code Playgroud)

"非词汇生命"这个名字对我来说听起来并不合适

值的生存期是值保持在特定内存地址的时间跨度(请参阅为什么我不能在同一结构中存储值和对该值的引用?以获得更长的解释).被称为非词汇生命周期的特征不会改变任何值的生命周期,因此它不能使生命周期变为非词汇.它只使跟踪和检查这些值的借用更加精确.

该功能的更准确的名称可能是"非词汇借用 ".一些编译器开发人员参考了底层的"基于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"
Run Code Online (Sandbox Code Playgroud)

如果你还在使用2015版本,那么在稳定的Rust中还没有非词汇生命周期.在每晚版本的Rust中,你可以通过一个功能选择加入NLL:

#![feature(nll)]
Run Code Online (Sandbox Code Playgroud)

您甚至可以使用编译器标志选择加入NLL的实验版本-Z polonius.

非词汇生命期解决的真实问题样本

  • 我认为值得强调的是,或许与直觉相反,非词汇生命期不是关于变量的生命周期,而是关于借用的生命周期.或者,否则说,非词汇生命期是关于将变量的生命期与借用的相关性去除...除非我错了?(但我不认为执行析构函数时NLL会发生变化) (11认同)
  • “ *有趣的是,某些特定的好的模式是由于词汇的生命周期而产生的*” –我想,那么,存在NLL的风险可能会使未来的好的模式变得更加难以识别吗? (2认同)
  • @eggyal 这当然有可能。在一组约束(即使是任意的!)内进行设计可以产生新的、有趣的设计。如果没有这些限制,我们可能会依赖于我们现有的知识和模式,而永远不会学习或探索以发现新事物。话虽如此,想必有人会想“哦,哈希值被计算了两次,我可以解决这个问题”,然后 API 就会被创建,但用户首先可能更难找到 API。我希望像 [clippy](https://github.com/rust-lang/rust-clippy) 这样的工具能帮助那些人。 (2认同)
  • 考虑到它特别缩短了绑定的生命周期估计,也许对命名的更好改进是**子词法生命周期**。此外,如上所述,地址粘性与生命周期没有任何关系,因为附加到向量(“push”)可以强制重新分配,因此可以更改其地址,而不会丢失其绑定的引用。对于这个新手来说,生命周期系统似乎都是关于绑定的:所有者、借用者和观察者(也称为共享)。想想看,Rust 中的观察者模式可能非常简单。 (2认同)

归档时间:

查看次数:

6149 次

最近记录:

6 年,10 月 前