有效期标注与变量依赖

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

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

例(0)

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

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

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

{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+

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

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

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

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

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}


fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

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

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

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

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

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

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

解:

依赖图如下:

upgit_20220421_1650477375.png

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

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

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

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

结构体与其成员的依赖

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

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

这里很简单,不详述。

有效期自动推断

例题,分析代码:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

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

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

upgit_20220421_1650509064.png

还有其他规则,此处略。

寿命表示法

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

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

impl<'a: 'b, 'b> ImportantExcerpt<'a> {
    fn foo(&'a self, name: &'b str) -> &'b str {
        println!("Attention please: {}", name);
        self.part
    }
}

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

static

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

参考

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

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