前言
生命周期标记主要用来处理生命周期跨函数时的情况。
标记形式
生命周期标记的一般形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
|
生命周期参数都有撇号(')作为前缀。如果标记的是引用,放在 &
后面。这个标记只起到修饰作用,不会影响参数的生命周期,可以理解为给编译器识别用的。
生命周期标记的一大用途就是显式声明包含关系。例如:
表示 ‘a 的生命周期一定大于等于 ‘b 的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
extern crate hello_rust;
fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32
where
'a: 'b,
{
println!("x={} y={}", x, y);
x
}
fn main() {
let num1: i32 = 1;
let mut num1_ref = &num1;
{
let num2: i32 = 2;
let mut num2_ref = &num2;
num1_ref = foo(num1_ref, num2_ref);
}
println!("{:?}", num1_ref);
}
|
在上例中,如果把 foo 的返回值的生命周期标记改为 ‘b ,程序将会报错,因为函数标记的返回值生命周期和 ‘b 相同,而 num1_ref 超出了这个范畴。
再看一个结构体的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#[derive(Debug)]
struct Type<'a> {
index: &'a i32,
}
fn main() {
let i = 0;
let mut t = Type { index: &i };
{
let i2 = 1;
t.index = &i2;
}
println!("{:?}", t);
}
|
结构体通过标记 ‘a 要求作为 Type 的字段 &i32 来说,&i32 至少要和 Type 的生命周期一样长。而本例中 i2 明显生命周期短于 t,所以程序就会报错。
通过上面两个例子相信对于生命周期标记的职责已经有了一定了解。传通过统的代码语言有时很难表述出这种关系,只能留给程序员自己维护,在复杂的软件中这种生命周期问题一旦遇到往往很难排查。Rust 并没有增加问题,只是增加了一种解决问题的方式。
特殊的 static
生命周期标记中有个特殊值 ‘static,它表示程序从开始到结束的整个生命周期。所以对于任意生命周期 ‘a 永远有 ‘static : ‘a。
生命周期标记的省略
生命周期标记很重要,但是如果所有需要的地方我们都要手动标记的话怕不是要累死人了。所以生命周期标记可以省略:
1
2
|
fn first_word<'a>(s: &'a str) -> &'a str {
fn first_word(s: &str) -> &str
|
省略的生命周期标记不会有复杂的自动推导过程,除了上例外只有三条简单的规则:
第一条:对于输入参数一人分配一个生命周期标记。
1
2
|
fn foo<'a>(x: &'a i32) // fn foo(x:i32)
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)// fn foo(x:i32, y:i32)
|
第二条:如果只有一个输入参数指定了生命周期标记,那么将它应用于所有返回参数。
第三条:如果多个输入参数制定了生命周期标记,但是存在 &self 或者 &mut self,所有返回参数应用 self 的标记。
其他情况需要手动标记。