Rust中的生命周期

74

偶尔我发现自己想编写可以以两种方式之一调用的函数:

// 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))
    }
}

问题在于我不理解我正在做什么。从我能够收集的信息中(主要来自编译器错误),我正在声明一个没有限制的生命周期,并将其用于描述路径参数(这意味着任何生命周期都可以作为参数传递)。

所以:

  • 我的理解是否准确?
  • 生命周期是什么?我可以在哪里了解更多关于它们的知识?
  • 在上面的示例中,类型为&str的参数和类型为&'a str的参数之间有什么区别?
  • 还有,'self是什么?

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


我对生命周期还不够自信,无法回答你的问题。但是,我可以告诉你,"data.txt" 的类型是 &'static str,它是一个静态分配的字符串。 - barjak
1个回答

75

更新于2015年5月16日:原问题中的代码适用于旧版本的Rust,但概念仍然相同。本答案已更新为使用现代Rust语法/库(基本上将~[]更改为Vec,将~str更改为String并调整示例代码)。

我的理解大致准确吗?
[...]
在上面的示例中,类型为&str的参数和类型为&'a str的参数之间有什么区别?

是的,像那样的生命周期本质上表示“没有限制”,有点像。生命周期是将输出值与输入连接起来的一种方式,即fn foo<'a, T>(t: &'a T) -> &'a T表示foo返回具有与t相同生命周期的指针,也就是说,它所指向的数据在与t相同的时间长度内有效(严格地说,至少是这样)。这基本上意味着返回值指向t指向的某些内存子部分。

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

Rust在编写&str时分配默认生命周期,这与编写未使用变量的生命周期完全相同。即fn(path: &str) -> Vec<String>与带有'a的版本没有区别。仅当省略生命周期与包括它时不同的时间是,如果您需要强制执行全局指针(即特殊的'static生命周期),或者如果要返回引用(例如-> &str),则仅当返回值具有生命周期时才可能(并且必须是一个或多个输入的寿命,或'static)。

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

一生命周期是指指针所指向的数据存在的保证时间,例如全局变量被保证会持续“永远”(因此它有特殊的生命周期'static)。一个巧妙的看待它们的方式是:生命周期将数据连接到其所有者所在的堆栈帧上;一旦该堆栈帧退出,所有者就会超出范围,任何指向/进入该值/数据结构的指针都不再有效,生命周期是编译器推理此事的一种方式。(使用堆栈帧视图,就好像@与当前任务关联了一个特殊的堆栈帧,而static则有一个“全局”堆栈帧)。
书中还有一个lifetimes chapter of the book,而this gist(注意,代码已过时,但概念仍然正确)是一个巧妙的小演示,展示了如何使用生命周期来避免复制/分配(具有强大的安全保证:没有悬空指针的可能性)。

顺便问一下,什么是'self

字面上并没有什么特别的,只是某些地方需要类型具有生命周期(例如在结构/枚举定义和impl中),目前仅接受'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
}

2
谢谢!看起来你说的对,我在这个例子中指定生命周期是多余的。我想我需要它来使用'static str方法,但在升级到0.7后没有重新测试。 - Daniel
1
刚刚看了教程,它(连同你的解释)很有帮助。再次感谢。我还有一堆 impl<'self> MyExtraFunction for &'self str(用于执行 "hello".my_extra_function()),但看起来我仍然需要它们。 - Daniel
@Daniel,没问题 :) (是的,impl 是需要借用指针具有生命周期的地方之一,即你目前不能编写 impl MyExtraFunction for &str。) - huon
现在没有像'self这样的东西了,对吧?当我谷歌搜索时,我发现有人建议使用'self来表示结构体实例的生命周期,但我没有找到任何追踪问题或RFC。没有关于'self的计划,对吧? - Mihir Luthra
1
@nalzok:在某种程度上你是对的,但这相当微妙。返回引用指向的实际值必须至少持续与“t”一样长的时间:如果该值持续时间更短,则引用可能会在“t”之前失效,这将是不好的。然而,编译器必须假设最短/最坏情况(返回值仅在“t”完全有效时有效),所以当“t”超出范围时,编译器禁止使用返回值。 - huon
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接