10.6 生命周期 Pt.2:生命周期的语法与例子
10.6.1. 生命周期标注语法
- 标注生命周期并不会改变引用存活多久。
- 如果某个函数指定了泛型生命周期参数,那么它可以接收带有任何生命周期的引用。
- 生命周期标注主要用于描述多个引用的生命周期之间的关系,但它们本身并不影响生命周期。
生命周期参数名必须以 ' 开头,通常是全小写且非常短。很多开发者使用 'a 作为生命周期参数名。
生命周期标注放在 & 符号后面,并在标注与引用类型之间用空格隔开。
10.6.2. 生命周期标注例子
&i32:一个普通引用&'a i32:带有显式生命周期的引用,被引用的类型是i32&'a mut i32:带有显式生命周期的可变引用
单个生命周期标注本身没有意义。生命周期标注的目的是向 Rust 描述多个泛型生命周期之间的关系。
以 10.5 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期 的代码为例:
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
}
}
longest 中参数 x、参数 y 以及返回值的生命周期都是 'a,这意味着 x、y 和返回值必须拥有“相同”的生命周期。
从上面的例子也可以看出,在函数签名中使用生命周期标注时,必须把泛型生命周期参数声明在 <> 里。这个签名告诉 Rust:存在一个生命周期 'a,并且 x、y 和返回值必须至少活得和 'a 一样长。
因为生命周期标注主要用于描述多个引用的生命周期之间的关系,但它们本身并不影响生命周期,所以这样写并不会改变实参的生命周期。它只是给借用检查器提供了一些可用于检测非法调用的约束。因此,longest 函数并不需要知道 x 和 y 到底活多久;它只需要某个作用域可以用来代替 'a,同时满足函数签名的约束即可。
当函数引用外部代码,或被外部代码引用时,几乎不可能单靠 Rust 编译器确定参数和返回值的生命周期。这样的函数所使用的生命周期可能在每次调用时都会变化。这正是有时需要手动标注生命周期的原因。
在示例代码中,当我们把具体引用传入 longest 函数时,用来代替 'a 的作用域是哪一块呢?就是 x 和 y 作用域的重叠部分,换句话说,是两者中较短的那个生命周期。 又因为返回值也有生命周期 'a,所以返回的引用在 x 和 y 作用域的重叠部分中保持有效。
这就是为什么在 10.5 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期 以及本文前面,都把“相同”一词放在引号里:它并不是字面意义上完全相同的生命周期,而是指重叠的部分。
接下来看看生命周期标注如何约束对 longest 的调用。如果我们修改上面的例子,让 string1 有不同的作用域,并把 string2 改成 String,会发生什么?
fn main() {
let string1 = String::from("abcd");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
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
}
}
这里,string1 的作用域是从第 2 行到第 8 行,string2 的作用域是从第 4 行到第 7 行。把它们传入 longest 时,函数会寻找重叠部分——或者说较短的那个生命周期——也就是 string2 的作用域,从第 4 行到第 7 行。因此 'a 所代表的作用域是从第 4 行到第 7 行。result 在内部作用域中有效,也就是直到第 7 行的结束花括号为止,所以代码在 'a 范围内仍然有效。
如果我改的是 result 的作用域呢?
fn main() {
let string1 = String::from("abcd");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
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
}
}
在这种情况下,string1 的作用域是从第 2 行到第 9 行,string2 的作用域是从第 5 行到第 7 行。把它们传入 longest 时,函数会寻找重叠部分——或者说较短的那个生命周期——也就是 string2 的作用域,从第 5 行到第 7 行。因此函数的泛型生命周期参数 'a 指的是从第 5 行到第 7 行的作用域,返回值也应该有同样的作用域。然而,接收返回值的 result 变量实际从第 3 行活到第 9 行,超出了 'a 所代表的作用域,所以程序会报错:
error[E0597]: `string2` does not live long enough
--> src/main.rs:6:44
|
5 | let string2 = String::from("xyz");
| ------- binding `string2` declared here
6 | result = longest(string1.as_str(), string2.as_str());
| ^^^^^^^ borrowed value does not live long enough
7 | }
| - `string2` dropped here while still borrowed
8 | println!("The longest string is {result}");
| ------ borrow later used here
编译器说 string2 活得不够长。为了保证第 8 行打印的 result 有效,string2 必须一直保持有效直到外部作用域结束。因为函数参数和返回值使用了相同的生命周期,Rust 才能指出这个问题。
让我们再重复一遍本文最重要的一点:'a 所代表的实际生命周期,是 x 和 y 两个生命周期中较短的那个。