Rust 生命周期(有效期)通俗解释

有效期标注与变量依赖

我不知道为什么大家写得那么啰嗦。花了十分钟搞懂之后,恍然大悟,其实就是八个字:必须确保依赖有效。下面均举某 Rust Course 书的例子。

定义,变量的有效期为:开始于其自身的原作用域之开始,结束于其依赖项的作用域结束。

例(0)

 1/*0*/{
 2/*1*/    let r;
 3/*2*/
 4/*3*/    {
 5/*4*/        let x = 5;
 6/*5*/        r = &x;
 7/*6*/    }
 8/*7*/
 9/*8*/    println!("r: {}", r);
10/*9*/}
  • r 的(原本)作用域:1~9 行。

  • 依赖的作用域:4~5 行。

因此,r 的真实有效期为 1~5 行。故第 8 行引用了无效的 r,因此编译报错。

(1)下面代码为何不对?

 1{
 2    let r;                // ---------+-- 'a
 3                          //          |
 4    {                     //          |
 5        let x = 5;        // -+-- 'b  |
 6        r = &x;           //  |       |
 7    }                     // -+       |
 8                          //          |
 9    println!("r: {}", r); //          |
10}                         // ---------+

答:因为 r 的有效期是 'a,r 对 x 的引用(r = &x;)在下一行随着 x 的失效而失效。故不安全。

这个道理很显然:若 a 依赖 b,则 b 的有效期应当大于 a

函数参数与返回值间的依赖

(2)下面代码为何不对?

 1fn main() {
 2    let string1 = String::from("abcd");
 3    let string2 = "xyz";
 4
 5    let result = longest(string1.as_str(), string2);
 6    println!("The longest string is {}", result);
 7}
 8
 9
10fn longest(x: &str, y: &str) -> &str {
11    if x.len() > y.len() {
12        x
13    } else {
14        y
15    }
16}

分析依赖:longest 的返回值(记作 a),a 依赖 x, y,而 x, y 的有效期未知,导致 r 的有效期未知,故不安全。

解决方法:标注返回值的生命周期 'a,到 x, y 上:

1fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
2    if x.len() > y.len() {
3        x
4    } else {
5        y
6    }
7}

从而表明返回值 a 的依赖项 x, y 具有相同有效期(或者说,x, y 的有效期大于等于 a)。从而通过检查。

(3)下面代码为何不对?

1/*1*/fn main() {
2/*2*/    let string1 = String::from("long string is long");
3/*3*/    let result;
4/*4*/    {
5/*5*/        let string2 = String::from("xyz");
6/*6*/        result = longest(string1.as_str(), string2./**/as_str());
7/*7*/    }
8/*8*/    println!("The longest string is {}", result);
9/*9*/}

解:

依赖图如下:

upgit_20220421_1650477375.png

注:A->B 表示 B 依赖于 A

可见 result 的实际有效期为整数区间 $[3,6]$。

print! 调用时,依赖的 result 已失效,故报错。

如何修改 longest 解决这个问题?注意到,罪魁祸首在于 string2,因此,除非改变函数对 string2 的依赖,否则无解。

结构体与其成员的依赖

结构由成员构成,则结构依赖于成员,则成员有效期必须大于结构有效期。

1struct ImportantExcerpt<'a> {
2    part: &'a str,
3}

这里很简单,不详述。

有效期自动推断

例题,分析代码:

 1fn first_word(s: &str) -> &str {
 2    let bytes = s.as_bytes();
 3
 4    for (i, &item) in bytes.iter().enumerate() {
 5        if item == b' ' {
 6            return &s[0..i];
 7        }
 8    }
 9
10    &s[..]
11}

解:设返回值为 a,则 a 只依赖于 s,故 a 的有效期为 s 的有效期。

要是不嫌累,也可以看依赖图:

upgit_20220421_1650509064.png

还有其他规则,此处略。

寿命表示法

(注:'a: 'b,表示有效期 a > b)

 1struct ImportantExcerpt<'a> {
 2    part: &'a str,
 3}
 4
 5impl<'a: 'b, 'b> ImportantExcerpt<'a> {
 6    fn foo(&'a self, name: &'b str) -> &'b str {
 7        println!("Attention please: {}", name);
 8        self.part
 9    }
10}

这表明实现方法 foo,其参数 b 比 self 短寿。并返回比和 b 同样短寿的值。这对于依赖关系是合法的。

static

'static 类似 C++ 的 static,表示静态有效期,即不依赖任何变量

参考

本文没讲各个消除规则,推荐继续阅读:

认识生命周期 - Rust语言圣经(Rust Course)