跳到主要内容

Rust生命周期

· 阅读需 7 分钟

当我们借用一个变量时,如果变量被销毁,那么我们就不能通过该借用的引用访问数据,因为变量以及被销毁了,此时访问的数据是不可知的。

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

如果我们尝试编译该代码,编译器将会给出错误

error[E0597]: `y` does not live long enough
--> hello5.rs:5:9
|
5 | x = &y;
| ^^ borrowed value does not live long enough
6 | }
| - `y` dropped here while still borrowed
7 | println!("{}", x);
| - borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

我们使用 x 变量借用 y 的引用,但是在 y 变量销毁以后,x 变量就不能访问 y 的数据了,它是一个悬空指针。好在我们不需要担心这个问题,编译器可以找到问题并为我们解决。编译器要解决这个问题,就得知道变量的声明周期,例如上面 x, y 的声明周期。

但是如果函数返回一个引用的话,函数是不知道返回的变量的声明周期

fn get_one(x: &i32, y: &i32) -> &i32 {
&x
}

如果我们尝试定义上面的函数,编译器会给出一个错误

error[E0106]: missing lifetime specifier
--> hello6.rs:13:33
|
13 | fn get_one(x: &i32, y: &i32) -> &i32 {
| ---- ---- ^ 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
|
13 | fn get_one<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
| ++++ ++ ++ ++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.

这是因为编译器不知道返回的引用的生命周期,如果不知道生命周期的话,它就不能检测悬空指针的问题,所以编译就不会通过,我们需要指定返回的引用的生命周期。

为了避免这样的事情发生,在我们定义函数时,需要为变量添加生命周期标记,生命周期标记使用 ' 开头,后面跟若干字符用于标记,一般我们使用单个字符,如 'a, 'b

fn get_one<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
&x
}

上面我们添加的 'a, 'b 就是生命周期标记,对于方法中使用的标记,都要在方法名后的 <> 中声明,例如 add<'a, 'b>,方法的输入分别被标记了不同的生命周期符号,x: &'a i32, y: &'b i32 表示它们具有不同的生命周期,返回的引用的生命周期时 'a,说明它与 x: &'a i32 的生命周期相同。

具有这些信息,编译器就可以检测悬空指针的问题了。看下面的一个例子:

fn main() {
let x = 1;
let z;
{
let y = 2;
z = get_one(&x, &y);
}

println!("{}", z);
}

fn get_one<'a, 'b>(x: &'a i32, y: &'b i32) -> &'b i32 {
&y
}

get_one 函数接收两个 i32 的引用,分别声明了不同的生命周期,然后返回一个 i32 的引用,其生命周期为 'b,与第二个参数的生命周期相同。

然后我们在 main 函数中调用 get_one,分别传入 &x, &y,返回的引用赋值给 z,返回的引用的生命周期与第二个参数相同,也就是与 y 相同,那么我们就不能在 y 的生命周期之外访问 z,否则就会导致悬空指针的问题,但是上面的程序中我们在 y 所在的块之外访问了 z,所以会编译出错

warning: unused variable: `x`
--> hello6.rs:13:20
|
13 | fn get_one<'a, 'b>(x: &'a i32, y: &'b i32) -> &'b i32 {
| ^ help: if this is intentional, prefix it with an underscore: `_x`
|
= note: `#[warn(unused_variables)]` on by default

error[E0597]: `y` does not live long enough
--> hello6.rs:6:21
|
6 | z = get_one(&x, &y);
| ^^ borrowed value does not live long enough
7 | }
| - `y` dropped here while still borrowed
8 |
9 | println!("{}", z);
| - borrow later used here

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0597`.

给出的错误是说,y 已经被销毁了,但是在后面被引用的 z 使用了。

如果我们修改上面的函数为

fn get_one<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
&x
}

这个时候我们返回的引用的生命周期是 'a,与第一个参数的生命周期相同,这个时候我们调用函数,因为返回的引用的生命周期与 x 的生命周相同,在 x 被销毁以前都可以使用,所以程序可以正确编译。

warning: unused variable: `y`
--> hello6.rs:13:32
|
13 | fn get_one<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
| ^ help: if this is intentional, prefix it with an underscore: `_y`
|
= note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

1

程序正确打印出了 x 的值 1(当然还给出了一些警告,因为我们在 get_one 函数里面只用了 x,没有用 y)。