Rust 的生命周期

Posted

技术标签:

【中文标题】Rust 的生命周期【英文标题】:Lifetimes in Rust 【发布时间】:2013-07-03 15:57:39 【问题描述】:

有时我发现自己想编写可以通过以下两种方式之一调用的函数:

// With a string literal:
let lines = read_file_lines("data.txt");

// With a string pointer:
let file_name = ~"data.txt";
let lines = read_file_lines(file_name);

我的第一个猜测是使用借用的指针 (&str) 作为参数类型,但是当它不起作用时(它只允许我使用 @str~str),我尝试了以下(通过复制 Rust 库),这确实有效。

fn read_file_lines<'a>(path: &'a str) -> ~[~str] 
    let read_result = file_reader(~Path(path));
    match read_result 
        Ok(file) => file.read_lines(),
        Err(e) => fail!(fmt!("Error reading file: %?", e))
    

问题是我不明白我在做什么。根据我所能收集到的信息(主要来自编译器错误),我声明了一个没有限制的生命周期,并使用它来描述路径参数(意味着任何生命周期都可以作为参数传递)。

所以:

我的理解是否准确? 什么是一生?我在哪里可以了解更多关于它们的信息? 上例中&amp;str类型的参数和&amp;'a str类型的参数有什么区别? 当我在做的时候,'self 是什么?

(我使用的是 Rust 0.7,如果它对答案有影响的话)

【问题讨论】:

我对生命周期没有足够的信心来回答你的问题。不过,我可以告诉你"data.txt"的类型是&amp;'static str,这是一个静态分配的字符串。 【参考方案1】:

2015-05-16 更新:原始问题中的代码适用于旧版本的 Rust,但概念保持不变。此答案已更新为使用现代 Rust 语法/库。 (基本上将~[] 更改为Vec~str 更改为String 并在最后调整代码示例。)

我的理解是否隐约准确? [...] 上例中 &str 类型的参数和 &'a str 类型的参数有什么区别?

是的,这样的一生基本上是在说“没有限制”。生命周期是将输出值与输入连接起来的一种方式,即fn foo&lt;'a, T&gt;(t: &amp;'a T) -&gt; &amp;'a T 表示foo 返回一个与t 具有相同生命周期的指针,即它指向的数据在与t(嗯,严格来说,至少只要)。这基本上意味着返回值指向t 指向的内存的某个子部分。

所以,像fn&lt;'a&gt;(path: &amp;'a str) -&gt; Vec&lt;String&gt; 这样的函数与编写 let x = 1; return 2; 非常相似......它是一个未使用的变量。

Rust 在编写&amp;str 时分配默认生命周期,这与编写未使用变量生命周期完全相同。即fn(path: &amp;str) -&gt; Vec&lt;String&gt;'as 的版本没有什么不同。唯一离开生命周期与包含生命周期不同的是,如果您需要强制执行全局指针(即特殊的 'static 生命周期),或者如果您想返回一个引用(例如 -&gt; &amp;str),这只有在以下情况下才有可能返回值有一个生命周期(这必须是一个或多个输入的生命周期,或者'static)。

什么是一生?我在哪里可以了解更多关于它们的信息?

生命周期是指针指向的数据保证存在多长时间,例如全局变量保证“永远”持续(所以它有特殊的生命周期'static)。查看它们的一种巧妙方法是:生命周期将数据连接到其所有者所在的堆栈帧;一旦该堆栈帧退出,所有者就会超出范围,并且任何指向/指向该值/数据结构的指针都不再有效,并且生命周期是编译器对此进行推理的一种方式。 (使用堆栈框架视图,就好像@ 有一个与当前任务关联的特殊堆栈框架,而statics 有一个“全局”堆栈框架。

还有一个 lifetimes chapter of the book 和 this gist(注意,代码现在已经过时,但概念仍然正确)是一个简洁的小演示,展示了如何使用生命周期来避免复制/分配(使用强大的安全保证:没有悬空指针的可能性。

当我在这里时,'self 是什么?

字面上没有什么特别的,只是某些地方需要类型有生命周期(例如在结构/枚举定义和impls 中),目前'self'static 是唯一被接受的名称。 'static 用于全局始终有效的指针,'self 用于可以有任何生命周期的东西。这是一个错误,将(非static)生命周期称为self 以外的任何内容都是错误的。


总而言之,我会这样写函数:

use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;

fn read_file_lines(path: &Path) -> Vec<String> 
    match File::open(path) 
        Ok(file) => 
            let read = BufReader::new(file);
            read.lines().map(|x| x.unwrap()).collect()
        
        Err(e) => panic!("Error reading file: ", e)
    


fn main() 
   let lines = read_file_lines(Path::new("foo/bar.txt"));
   // do things with lines

【讨论】:

谢谢!看起来您在我的示例中指定生命周期是多余的是正确的。我想我需要 0.6 才能使用 'static strs 的方法,但升级到 0.7 后没有重新测试。 只需阅读本教程:它(连同您的解释)很有帮助。再次感谢。我还有一堆impl&lt;'self&gt; MyExtraFunction for &amp;'self str(要做"hello".my_extra_function()),但看起来我仍然需要这些。 @Daniel,没问题 :) (是的,impls 是借来的指针需要有生命周期的地方之一,即你现在不能写 impl MyExtraFunction for &amp;str。) (另外,join us on IRC,如果你还没有;通常有几个人愿意提供帮助。) @nalzok:从某种意义上说,您是对的,但这相当微妙。返回的引用所指向的实际值必须至少与t 一样长:如果该值的持续时间较短,则该引用可能在t 之前失效,这会很糟糕。但是,编译器必须假设最短/最坏的情况(返回值与t 一样有效),所以当t 超出范围时,编译器不允许使用返回值是对的。

以上是关于Rust 的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

使用字符串时 Rust 中的生命周期

Rust 的生命周期

了解生命周期:最大生命周期和“静态”

我啥时候需要在 Rust 中指定显式生命周期?

指定 Rust 闭包的生命周期

为啥 Rust 编译器要求我限制泛型类型参数的生命周期(错误 E0309)?