Rust 生命周期中的一些坑

5 min read

前面有一篇文章介绍了什么是 Rust 的生命周期?这里补充一些关于 Rust 的生命周期不容易被注意到的细节。

因为我水平有限,有的概念可能不是很全面,甚至可能有偏差,还请多多指点。

生命周期是完全静态的

Rust 的看家本领就是静态检测,所有实例的生命周期都是在编译时就被确定好的。

所以不要问:“为什么生命周期不能根据实际情况自动确定?”因为真的做不到!

let 可以绑定所有权

除了绑定所有者和数据外,let 还可以绑定所有者和数据之间的所有权。绑定了所有权,也就绑定了生命周期。

这是理所当然的事实,但是经常被人忽略。

形参与实参的生命周期是相互绑定的

let 相似,函数中的形式参数与实际参数的生命周期是相互绑定的。同样的,函数返回值和函数实体的拥有者的生命周期也是相互绑定的,因为返回值的生命周期与函数实体相互绑定,而函数实体与拥有者相互绑定。

举个例子:

fn sum(x: u32, y: u32) -> u32 {
      x + y
 }

 fn main(){
     let a = 1;
     let b = 2;
     let c = sum(a, b);
 }

xa 相互绑定,csum()a + b 相互绑定。

a -> x
x + y -> sum() -> c

结构体中也是类似的:

struct Foo {
    x: i32,
}

fn main() {
    let y = 5;
    let f = Foo { x: y };
}

xy 相互绑定,结构体 Foof 相互绑定。

x -> a
Foo -> f

这同样也是理所当然但容易被人忽视的事实,这里提出来也是为了给下一条做铺垫。

生命周期标记的影响不仅仅是函数 (结构体) 内部

准确的说应该是生命周期标记可以通过影响函数 (结构体) 内部来对函数实体的拥有者和传入的实际参数产生影响。具体原因就是上面提到的形式参数与实际参数的生命周期相互绑定,函数返回值和函数实体的拥有者的生命周期相互绑定。

举个例子:

fn foo<'a>(x: &'a str, y: &str) -> Vec<&'a str> {
    let mut results = Vec::new();

    if x.contains(y) {
        results.push(x);
    }

    results
}

fn main() {
    let a = "I'm foo";
    let b = "foo";
    let c = foo(a, b);
}

生命周期参数将 x 的生命周期与 results 相互绑定,而 xa 相互绑定,resultsc 相互绑定,所以 c 的生命周期与 a 是相互绑定的!!{{< spoiler >}}就像三国杀中的“铁索连环”一样{{< /spoiler >}}

a -> [x => results -> foo] -> c

根据关系我们可以很容易发现,a 必须活得比 c 长,不然就会报错。

从整体来考虑生命周期,显然更容易理解什么时候应该使用生命周期标注,什么时候不应该使用。

生命周期标注的作用不是“续命”

生命周期标注只是注释出:“谁应该比谁活得更长 (或者相等)”,如果实际情况与标注不相符,编译器会报错。

长生命周期是短生命周期的子类型

与直觉有所不同。显式生命周期标注是泛型参数一种,但是有协变的概念。

相关文章