深入学习Rust生命周期
生命周期规则
我们先了解这里约束的是什么,建议直接去看英文原版的官方写的rust book。生命周期是变量从创建到销毁的有效区间。
-
在函数参数和返回值约束中,有相同生命周期的参数’a的变量x,y,z,意味着存在一个 ‘a 区间,使得x,y,z三个变量都是有效的,也即这个区间的初始点是所有变量创建时初始点中最靠后的,结束点是所有变量销毁时的结束点中最靠前的。
-
结构体定义中的生命周期注释,是数据引用的生命周期,表示结构体的生命周期在引用的生命周期之内,也是通过求解找到区间。
1
2
3
4
5
6
7
8
9struct 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 lifetimes和output lifetimes,表示参数和返回值。
- 在函数中,每个引用参数分配一个生命周期参数。
- 如果只有一个输入生命周期参数,则该生命周期将分配给所有输出生命周期参数。
- 方法的第一个参数,除非是new方法,一般是
&self
&mut self
self
Box<self>
、Rc<self>
等。那么如果是&self
&mut self
,它的生命周期分配给所有参数。
循环引用
https://course.rs/compiler/fight-with-compiler/lifetime/too-long1.html
这个例子最大的特殊点在于,我们平常都是函数、结构体、方法中引用数据的生命周期是单独介绍的。但是这里搞复杂了,增加了各种奇怪的约束,比如说我们看一些组合起来的写法。
1 | impl<'a> List<'a> { |
在方法中 &'a mut self
,实际上参数是 &'a mut List<‘a>
类型 。因为方法可以写成函数
1 | pub fn get_interface(&'a mut List<'a> input) -> Interface<'a> { |
对于这种写法,混合了结构体的生命周期约束和函数参数的生命周期约束。可以叫做「生命周期绑定」或者叫做「生命周期纠缠」,编译器认为这个借用永远不会结束。表示
- 对
self
的可变借用将持续到'a
结束,而不仅仅是方法调用结束。方法结束之后self还是被借用着。 所以在'a
的整个生命周期内,你不能再次借用这个结构体实例。 - 方法返回的值和self的生命周期绑定,绑定的意思是只会同时有效和同时无效。即使
Interface
对象被丢弃。
写代码需要有一个原则,避免引用循环,类似 &'a SomeType<'a>
。所以改成下面:
1 | struct Interface<'b,'a> { |
但是根据规则3,&mut self
的生命周期会分配个给 Interface
。实际上编译器推断自动补全的标注是
1 | impl<'a> List<'a> { |
但是因为创建的Interface使用到了 &mut self.manager<'a>
的生命周期 'a
,编译器认为返回值是 Interface<'_,'a>
。也就是说编译器推断的不对的,我们自己手动补充了。
1 | impl<'a> List<'a> { |
最终建议
再次强调,不要可变引用循环,类似 &mut'a SomeType<'a>
。因为Rust中类型对生命周期参数的 variance 行为。
-
不可变引用对生命周期是协变的(covariant):意味着较长生命周期的引用可以"缩小"为较短生命周期的引用。
-
可变引用对生命周期是不变的(invariant):意味着不允许生命周期的任何转换,必须严格匹配。
不可变的循环引用,是可以的。比如改动 struct Interface,去掉
1 | struct Interface<'a> { |
也要避免可变引用的传递 & SomeType<'a>->AnotherType<'a,'a'>
,因为你需要手动对应一下生命周期在哪个位置。能用Rc就别折腾,性能损耗没多大。
第三种,下面的例子也是可变循环引用。m1类型是 &'_ mut S<'a>
,但是后续m1创建了自引用,这是特殊的循环引用,所以 m1 的类型是 &'a mut S<'a>
,这违反了我们提到的规定,不要这么用。
1 | struct S<'a> { |
参考:https://stackoverflow.com/a/66253247/18309513 以及它提到的回答。