繁体   English   中英

Rust 寿命背后的直觉是什么?

[英]What is the intuition behind Rust lifetimes?

我已经从许多不同的资源中阅读了 Rust 生命周期的概念,但我仍然无法弄清楚它背后的直觉。 考虑这段代码:

#[derive(Debug)]
struct Example<'a> {
    name: &'a str,
    other_name: String,
}

fn main() {
    let a: &'static str = "hello world";
    println!("{}", a);

    let b: Example = Example {
        name: "Hello",
        other_name: "World".into(),
    };
    println!("{:?}", b);
}

在我的理解中,Rust 中的所有东西都有一个生命周期。 在行中let a: &'static str = "hello world"; 变量a在程序结束之前一直保持活动状态,并且'static是可选的,即let a: &str = "hello world"; 也是有效的。 我的困惑是当我们向其他人添加自定义生命周期时,例如 struct Example

struct Example<'a> {
    name: &'a str,
    other_name: String,
}

为什么我们需要为它附加一生'a 为什么我们在 Rust 中使用生命周期的简化和直观推理是什么?

如果您来自垃圾收集语言的背景(我看到您熟悉 Python),那么整个生命周期的概念确实会让人感到非常陌生。 即使是相当高级的 memory 管理概念,例如堆栈和堆之间的区别或何时发生分配和释放,也可能难以掌握:因为这些是垃圾收集对您隐藏的细节(需要付出代价)。

另一方面,如果您来自必须自己管理 memory 的语言(例如 C++),那么这些概念您已经很熟悉了。 我的理解是,Rust 主要设计用于在这个“系统语言”领域竞争,同时引入策略(如借用检查器)来帮助避免大多数 memory 管理错误。 因此,许多文档都是在考虑到这些受众的情况下编写的。

在你真正理解“生命周期”之前,你应该掌握堆栈和堆。 生命周期问题主要出现在(或可能)堆上的东西上。 Rust 的所有权 model 最终是关于将每个堆分配与特定的堆栈项相关联(可能通过其他中间堆项),这样当一个项从堆栈中弹出时,其所有相关的堆分配都被释放。

然后问自己,每当您引用(即 memory 地址)某物时:当使用该引用时,该东西是否仍位于 memory 的预期位置? 它可能不是的一个原因是因为它在堆上并且它拥有的项目已经从堆栈中弹出,导致它被丢弃并且它的 memory 分配被释放; 另一个可能是因为它已重新定位到 memory 中的某个其他位置(例如,它是一个Vec ,它超出了之前分配的可用空间)。 即使只是数据的突变也可能违反对那里保存的内容的期望,因此它们也不允许在您的下方发生。

要掌握的最重要的一点是,Rust 的生命周期对这个问题没有任何影响:也就是说,它们永远不会影响某物在 memory 位置保留多长时间——它们只是我们对那个问题的答案和代码所做的断言如果无法验证这些断言,则不会编译。

因此,以您的示例为例:

struct Example<'a>{
    name: &'a str,
    other_name: String,
}

假设我们创建了这个结构的一个实例:

let foo = Example { name: "eggyal", other_name: String::from("Eka") };

现在假设这个foo ,一个堆栈项,位于地址0x1000 深入研究典型 64 位系统的实现细节,我们的 memory 可能看起来像这样:

...
0x1000 foo.name#ptr       = 0xabcd
0x1008 foo.name#len       = 6
0x1010 foo.other_name#ptr = 0x5678
0x1018 foo.other_name#len = 3
...
0x5678 'E'
0x5679 'k'
0x567a 'a'
...
0xabcd 'e'
0xabce 'g'
0xabcf 'g'
0xabd0 'y'
0xabd1 'a'
0xabd2 'l'
...

请注意,在foo中, nameother_name均仅由一个指针和一个长度组成。 那么&strString有什么区别呢? 这就是管理相关 memory 分配的责任所在

  • 由于String是一个拥有的、堆分配的字符串, foo.other_name “拥有”(负责)其相关的 memory 分配——因此,当foo被删除(例如,因为它从堆栈中弹出)时,Rust 将确保那些地址0x5678处的三个字节被释放并返回给分配器(最终通过std::ops::Drop的实现发生)。 拥有分配也意味着String可以安全地改变 memory,将值重新定位到另一个地址等(前提是它当前没有借给其他地方)。

  • 相比之下,位于 0xabcd 的 memory 分配不是0xabcd “拥有”的foo.name我们说它是“借用”分配——但如果foo.name不管理分配,它如何确定它包含它的内容应该? 好吧,我们程序员 promise Rust我们将在借用期间保持内容有效(我们给它一个名称,在您的情况下, 'a : &'a str 'a str被借用的终身),借用检查器确保我们保留我们的 promise。

    但是,我们承诺这一'a会持续多久? 好吧,对于Example的每个实例都会有所不同:我们 promise "eggyal"将在0xabcdfoo的时间段很可能与我们 promise 某些name值的时间段完全不同其他实例将在其地址。 所以我们的生命周期'aExample的一个参数:这就是为什么它被声明为Example<'a>

    幸运的是,我们不需要明确定义我们的生命周期实际上会持续多长时间,因为编译器知道一切的实际生命周期,只需要检查我们的断言是否成立:在我们的示例中,编译器确定提供的值"eggyal"是一个字符串字面量,因此属于&'static str类型,因此在'static生命周期内将位于其地址0xabcd ; 因此在foo的情况下, 'a被允许是“任何生命周期直到并包括'static ”; 在@Aloso 的回答中,您可以看到一个具有不同生命周期的示例。 然后,无论在哪里使用foo ,都可以根据这个确定的界限检查和验证该使用站点上的任何生命周期断言。

这需要一些时间来适应,但我发现像这样描绘 memory 布局并问自己“ memory 分配何时被释放? ”帮助我理解代码中的生命周期(有时我需要考虑何时该值可能是重定位或突变,但仅仅考虑释放通常就足够了——而且通常更容易掌握)。

在这一行中let a:&'static str = "hello world"; 变量a一直保持到程序结束

不,这不是发生的事情。 a是一个引用,即它引用一些字符串数据。 该字符串数据是'static ,这意味着它在程序结束之前仍然有效。 然而, a不需要在程序结束时处于活动状态(它恰好在这种情况下,因为它是在main function 中声明的第一个值,但这只是巧合)。

当一个struct有生命周期时,这通常意味着它借用了另一个值,并且只能在该值存在时使用。 例如

struct Example<'a> {
    name: &'a str,
    other_name: String,
}

fn main() {
    let s: String = "hello world".to_string();
    let example = Example {
        name: &s[..],
        other_name: "".to_string(),
    };
    drop(s); // the lifetime of s ends here

    // however, example borrows s, therefore its lifetime
    // is tied to s. This means that example can't be used
    // after s was dropped. Therefore, the following line
    // will trigger a compiler error:
    println!("{}", example.name);
}

实际报错信息中的推理略有不同,但我觉得还是比较容易理解的:

error[E0505]: cannot move out of `s` because it is borrowed
  --> src/main.rs:12:10
   |
9  |         name: &s[..],
   |                - borrow of `s` occurs here
...
12 |     drop(s); // the lifetime of s ends here
   |          ^ move out of `s` occurs here
...
18 |     println!("{}", example.name);
   |                    ------------ borrow later used here

错误消息指出,借用s example是在s被删除后使用的。 这是被禁止的,因为example的生命周期不能超过s

我希望我能让你更好地理解它是如何工作的。 我还建议您阅读 Rust 书的这一章和这一章。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM