前言

先说大白话,rust 的生命周期标注,是为了明确多个变量的生命周期是否一致,仅此而已,因为如果rust不知道多个变量的生命周期是否一致,它无法确的知道这个变量是否已经被释放。这个下面再细说,先说有什么用。

rust当中,的两个重要概念:借用生命周期分别代是在:

  1. 栈变量,需要关注【所有权】
  2. 引用(指针),需要关注【生命周期】

Rust 的每个引用都有自己的生命周期,生命周期指的是引用保持有效的作用域。
大多数情况下,引用是隐式的、可以被推断出来的,但当引用可能以不同的方式互相关联时,则需要手动标注生命周期。
这里重点就是以不同的方式互相关联时

大多数情况下,rust 可以自己推断出引用的生拿周期,也就是只有在一些rust无法自行推断的情况下,才需要手动标注生命周期。

生命周期

Rust 中的每一个引用都有其生命周期(lifetime),也就是引用保持有效的作用域。
大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。
类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。

这里还有一个需要关注的点就是关系,也就多个引用之前的关系,才是导致rust无法明确推断出引用生命周期的最根本原因。

反例

这段代码看着很正常,但是实际上,编译会报错,类为这里调用longest时,longest无法确认xy的生命周期。
为什么无法确认?
因为longest是被调用的方法,它肯定没法知道,这两个传入在main方法的中的生命周期。
好比,你写一个接口给外部调用,你也无法知道调你的服务,传入的两个变量,在那个服务中的生命周期。
但是在rust中,又非常强调安全性,它必须清楚每个引用的明确的生命周期。
所以这个活,就落在了开发者身上,必须明确告诉rust,每个引用的生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

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

// longest函数 无法确认 x、y 在 mian 函数中的生命周期
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

报错如下:missing lifetime specifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error[E0106]: missing lifetime specifier
--> src/main.rs:9:33
|
9 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ++++ ++ ++ ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `playground` due to previous error

上面看着很正常呀,哪里有问题?

生命周期标注

即然rust不智能,那只能开发者辛苦一点,手动来标注了。
rust的生命周期标注语法,只能表示引用的生命周期,而不能、不会改会引用的生命周期。

命名规则:

  1. 'a 以 ' 开头
  2. 全小写
1
2
3
&i32        // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。

函数签名中的生命周期注解

描述了 x、y 之间的关系。
longest 函数定义指定了签名中所有的引用必须有相同的生命周期'a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

只有一个参数,要不要标注

那当然是不要啦!!
生命周期注解告诉编译器引用参数的有效范围,以便编译器可以检查代码是否合法。
但是,在某些情况下,编译器可以自动推断出引用参数的生命周期,因此不需要显式注解。

当一个函数或方法需要一个借用参数时,如果该参数的生命周期与函数或方法的生命周期相同,则可以省略生命周期注解。例如:
这个例子,标不标注都是成立的。

1
2
3
4
5
6
7
8
9
fn foo<'a>(x: &'a i32) -> &'a i32 {
x
}

fn main() {
let x = 5;
let y = foo(&x);
println!("{}", y);
}

但是,如果函数或方法需要一个借用参数,并且该参数的生命周期与函数或方法的生命周期不同,则必须显式注解参数的生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Foo<'a> {
x: &'a i32,
}

impl<'a> Foo<'a> {
fn bar(&self, y: &'a i32) -> &'a i32 {
if *y > 0 {
y
} else {
self.x
}
}
}

fn main() {
let x = 5;
let y = 6;
let foo = Foo { x: &x };
let z = foo.bar(&y);
println!("{}", z);
}

在这个例子中,方法 bar 的第二个参数 y 的生命周期不同于 Foo 结构体中的引用 x 的生命周期,所以嘛必须显式注解参数的生命周期。

总结

人多了,就容易产生纠分,变量形参多了,也是这样,所以才需要标注,分个明白。