什么是非词汇寿命?


Answers:


130

通过了解什么是词法生存期,最容易理解什么是非词法生存期。在存在非词汇生存期之前的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从来没有使用过!问题是scoresby的借用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

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


11
我认为值得强调的是,非直觉式的生命周期与变量的生命周期无关,而是与借贷的生命周期有关,这可能是违反直觉的。或者,换句话说,非词法生命周期是关于将变量的生命周期与借用生命周期解相关...除非我错了?(但我不认为执行析构函数时NLL会发生变化)
Matthieu M.

2
有趣的是,某些特定的良好模式是由于词汇的生命周期而产生的”-我想,那么,存在NLL的风险可能使未来的良好模式变得更加难以识别吗?
eggyal

1
@eggyal当然是有可能的。在一组约束(即使是任意约束!)内进行设计也可以带来有趣的新设计。没有这些限制,我们可能会依靠现有的知识和模式,从不学习或探索以寻找新的东西。话虽这么说,大概有人会认为“哦,哈希值被计算了两次,我可以解决这个问题”,并且将创建API,但对于用户而言,一开始可能很难找到该API。我希望像clippy这样的工具帮助那些人。
Shepmaster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.