Rust 生命周期中的一些坑
前面有一篇文章介绍了什么是 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);
}
x
与a
相互绑定,c
与 sum()
与 a + b
相互绑定。
a -> x
x + y -> sum() -> c
结构体中也是类似的:
struct Foo {
x: i32,
}
fn main() {
let y = 5;
let f = Foo { x: y };
}
x
与 y
相互绑定,结构体 Foo
与 f
相互绑定。
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
相互绑定,而 x
与 a
相互绑定,results
与 c
相互绑定,所以 c
的生命周期与 a
是相互绑定的!!{{< spoiler >}}就像三国杀中的“铁索连环”一样{{< /spoiler >}}
a -> [x => results -> foo] -> c
根据关系我们可以很容易发现,a
必须活得比 c
长,不然就会报错。
从整体来考虑生命周期,显然更容易理解什么时候应该使用生命周期标注,什么时候不应该使用。
生命周期标注的作用不是“续命”🔗
生命周期标注只是注释出:“谁应该比谁活得更长 (或者相等)”,如果实际情况与标注不相符,编译器会报错。
长生命周期是短生命周期的子类型🔗
与直觉有所不同。显式生命周期标注是泛型参数一种,但是有协变的概念。