生命周期规则

我们先了解这里约束的是什么,建议直接去看英文原版的官方写的rust book。生命周期是变量从创建到销毁的有效区间。

  • 在函数参数和返回值约束中,有相同生命周期的参数’a的变量x,y,z,意味着存在一个 ‘a 区间,使得x,y,z三个变量都是有效的,也即这个区间的初始点是所有变量创建时初始点中最靠后的,结束点是所有变量销毁时的结束点中最靠前的。

  • 结构体定义中的生命周期注释,是数据引用的生命周期,表示结构体的生命周期在引用的生命周期之内,也是通过求解找到区间。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct ImportantExcerpt<'a,'b,'c,'d,'e> {
    part1: &'a str,
    part2: &'b str,
    part3: &'c str,
    part4: &'d Me<'e>
    }
    struct Me<'a>{
    part5: &'a mut Me<>,
    }

    根据规则,我们知道结构体的生命周期,在 a 到 e的交集中。其中潜在的 生命周期d也应该在e之中,因为这是 Me 结构体的约束。至于a,b,c三者之间,没有互相的约束。

  • 对于结构体实现的声明周期(impl后面的),其实就是结构体中引用数据的生命周期,和方法中参数的生命周期没有联系。

生命周期还有3条推导规则。注意术语: input lifetimesoutput lifetimes,表示参数和返回值。

  1. 在函数中,每个引用参数分配一个生命周期参数。
  2. 如果只有一个输入生命周期参数,则该生命周期将分配给所有输出生命周期参数
  3. 方法的第一个参数,除非是new方法,一般是 &self &mut self self Box<self>Rc<self>等。那么如果是 &self &mut self,它的生命周期分配给所有参数。

循环引用

https://course.rs/compiler/fight-with-compiler/lifetime/too-long1.html

这个例子最大的特殊点在于,我们平常都是函数、结构体、方法中引用数据的生命周期是单独介绍的。但是这里搞复杂了,增加了各种奇怪的约束,比如说我们看一些组合起来的写法。

1
2
3
4
5
impl<'a> List<'a> {
pub fn get_interface(&'a mut self) -> Interface<'a> {
// ...
}
}

在方法中 &'a mut self,实际上参数是 &'a mut List<‘a> 类型 。因为方法可以写成函数

1
2
3
pub fn get_interface(&'a mut List<'a> input) -> Interface<'a> {
// ...
}

对于这种写法,混合了结构体的生命周期约束和函数参数的生命周期约束。可以叫做「生命周期绑定」或者叫做「生命周期纠缠」,编译器认为这个借用永远不会结束。表示

  • self 的可变借用将持续到 'a 结束,而不仅仅是方法调用结束。方法结束之后self还是被借用着。 所以在 'a 的整个生命周期内,你不能再次借用这个结构体实例。
  • 方法返回的值和self的生命周期绑定,绑定的意思是只会同时有效和同时无效。即使Interface 对象被丢弃。

写代码需要有一个原则,避免引用循环,类似 &'a SomeType<'a>。所以改成下面:

1
2
3
4
5
6
7
8
9
struct Interface<'b,'a> {
manager: &'b mut Manager<'a>
}

impl<'a> List<'a> {
pub fn get_interface(&mut self) -> Interface {
// ...
}
}

但是根据规则3,&mut self 的生命周期会分配个给 Interface。实际上编译器推断自动补全的标注是

1
2
3
4
5
6
7
impl<'a> List<'a> {
pub fn get_interface<'b>(&'b mut self) -> Interface<'b,'b> {
Interface {
manager: &mut self.manager
}
}
}

但是因为创建的Interface使用到了 &mut self.manager<'a> 的生命周期 'a,编译器认为返回值是 Interface<'_,'a> 。也就是说编译器推断的不对的,我们自己手动补充了。

1
2
3
4
5
6
7
impl<'a> List<'a> {
pub fn get_interface<'b>(&'b mut self) -> Interface<'b,'a> {
Interface {
manager: &mut self.manager
}
}
}

最终建议

再次强调,不要可变引用循环,类似 &mut'a SomeType<'a>。因为Rust中类型对生命周期参数的 variance 行为。

  1. 不可变引用对生命周期是协变的(covariant):意味着较长生命周期的引用可以"缩小"为较短生命周期的引用。

  2. 可变引用对生命周期是不变的(invariant):意味着不允许生命周期的任何转换,必须严格匹配。

不可变的循环引用,是可以的。比如改动 struct Interface,去掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct Interface<'a> {
manager: &'a Manager<'a>
}

impl<'a> Interface<'a> {
pub fn noop(self) {
println!("interface consumed");
}
}

struct Manager<'a> {
text: &'a str
}

struct List<'a> {
manager: Manager<'a>,
}

impl<'a> List<'a> {
pub fn get_interface(&'a self) -> Interface {
Interface {
manager: &self.manager
}
}
}

fn main() {
let mut list = List {
manager: Manager {
text: "hello"
}
};

list.get_interface().noop();

println!("Interface should be dropped here and the borrow released");

// this fails because inmutable/mutable borrow
// but Interface should be already dropped here and the borrow released
use_list(&list);
}

fn use_list(list: &List) {
println!("{}", list.manager.text);
}

也要避免可变引用的传递 & SomeType<'a>->AnotherType<'a,'a'>,因为你需要手动对应一下生命周期在哪个位置。能用Rc就别折腾,性能损耗没多大。

第三种,下面的例子也是可变循环引用。m1类型是 &'_ mut S<'a>,但是后续m1创建了自引用,这是特殊的循环引用,所以 m1 的类型是 &'a mut S<'a>,这违反了我们提到的规定,不要这么用。

1
2
3
4
5
6
7
struct S<'a> {
i: i32,
r: &'a i32,
}
let mut s = S { i: 0, r: &0 };
let m1 = &mut s;
m1.r = &m1.i; // 此时s.r引用了s.i,创建了自引用

参考:https://stackoverflow.com/a/66253247/18309513 以及它提到的回答。