Rust 为什么需要生命周期注解
fn main() {
let b;
{
let a = 10;
b = &a;
}
println!("{}", b);
}
此时,a的作用域比b要小。rust的生命周期检查器发现a的生命周期比b要大,但是拥有一个生命周期比它小的对象,编译器就会拒绝编译。简而言之,rust的编译器能帮助我们确定某个引用是否有效(无效的话在编译时就会报错),以此来避免悬垂引用等一系列问题。
然而,现实是,编译器有时候并不能确定某个引用是否有效。我们来看下面这个例子:
fn longest_str(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let str1 = String::from("123");
let str_longer;
{
let str2 = String::from("12345");
str_longer = longest_str(&str1, &str2);
}
println!("{}",str_longer);
}
这段代码是无法编译通过的,报错信息如下:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:37
|
1 | fn longest_str(x: &str, y: &str) -> &str {
| ^ expected 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`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0106`.
error: Could not compile `lifetime_test`.
To learn more, run the command again with --verbose.
表明在函数longest_str里需要生命周期注解,这是为什么呢?我们来分析一下代码。在上面这段代码里,str_longer拥有的引用是否有效呢?答案是不确定。我们只从函数内部来看是无法确认哪个参数的生命周期长或者是短.
需要注意的是,生命周期注解并不改变任何引用的生命周期的长短。也就是说,单个生命周期其实是没什么意义的,生命周期最大的作用就是告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。加入有一个有一个生命周期为'a的参数x,一个生命周期也为'a的参数y,那么就说明x与y的生命周期是一样长的。这么一来就可以解决刚才longest_str函数的问题了。我们给函数标上生命周期注解:
fn longest_str<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let str1 = String::from("123");
let str_longer;
{
let str2 = String::from("12345");
str_longer = longest_str(&str1, &str2);
}
println!("{}",str_longer);
}
虽然仍然无法编译通过,但是错误信息变成了:
error[E0597]: `str2` does not live long enough
--> src/main.rs:14:41
|
14 | str_longer = longest_str(&str1, &str2);
| ^^^^^ borrowed value does not live long enough
15 | }
| - `str2` dropped here while still borrowed
16 |
17 | println!("{}",str_longer);
| ---------- borrow later used here
即错误信息变成了编译器检查出引用生命周期无效了,而不是longest_str这个函数的错误了,即str2和str1生命周期不一致。我们只需要修改main函数里参数的生命周期,使str1与str2生命周期保持一致即可。
fn longest_str<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let str1 = String::from("12345");
let str_longer;
let str2 = String::from("123");
str_longer = longest_str(&str1, &str2);
println!("{}",str_longer);
}
那么问题来了,在上面这段代码里,函数longest_str里,x,y和函数返回值的生命周期都是一致的,那我们可不可以将它们标注成不同的呢?就像
fn longest_str<'a, 'b, 'c>(x: &'a str, y: &'b str) -> &'c str {
if x.len() > y.len() {
x
} else {
y
}
}
这并没有任何意义, 生命周期注解说到底也只是起一个标识作用.并不会改变程序的任何逻辑.它只是用来给Rust生命周期检查器核对的.
最后,总结一下,当且仅当输出值的生命周期为所有输入值的生命周期交集的子集时,生命周期合法。这个应该很好理解,也很好的解释了我们举的例子中生命周期注解的用法。rust也正是通过这种方式解决了"共享"这一问题,从某种程度上保证了内存的安全。
总结
生命周期注解只是给编译器检查使用的。
注解本身无法改变任何生命周期。
但是标出后能尽量让程序和你预期的一样工作。