什么是非词汇生存期



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

我的问题是:非词汇生命周期到底是什么?

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

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

Rust 编译器看到scoresscore变量借用,因此它不允许进一步改变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词汇的——它一直持续到包含它的块的末尾:

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 的借用"。

非词汇生存期本身从未打算成为"面向用户"的功能。它们在我们的脑海中大多变得很大,因为我们从它们的缺席中得到了一些小剪纸。他们的名字主要用于内部开发目的,出于营销目的更改它从来都不是优先事项。

是的,但是我该如何使用它?

在 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 的实验版本。

非词汇生命周期解决的实际问题示例

  • 从 HashMap 或 Vec 返回引用会导致借用持续超出其范围?
  • 为什么 HashMap::get_mut() 在其余范围内拥有映射的所有权?
  • 不能借用为不可变,因为它在函数参数中也被借用为可变
  • 如何在 Vec 上更新或插入?
  • 有没有办法在绑定超出范围之前释放绑定?
  • 迭代递归结构时无法获得可变引用:一次不能多次借用可变
  • 引用
  • 在返回使用标准锁的结果时,为什么保留对标准锁的借款?
  • 解构一盒对时附带移动错误

相关内容

  • 没有找到相关文章

最新更新