Skip to content

生命周期

生命周期是 Rust 另一个独特的概念。它帮助编译器确保所有引用都是有效的,防止悬垂引用。

考虑以下代码:

fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

这段代码无法编译。为什么?

编译器不知道返回的引用是来自 x 还是 y,因此无法确定返回值的有效期。返回的引用必须在被使用时仍然有效,但编译器无法保证这一点。

fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
}
// 如果 result 是 string2 的引用,这里就是悬垂引用!
// println!("{}", result);
}

生命周期标注不会改变引用的存活时间,只是描述多个引用之间的关系。

生命周期参数以 ' 开头,通常使用小写字母:

&i32 // 引用
&'a i32 // 带有生命周期 'a 的引用
&'a mut i32 // 带有生命周期 'a 的可变引用
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

这段代码的含义:

  • <'a> 声明一个生命周期参数
  • x: &'a stry: &'a str 表示两个参数有相同的生命周期
  • -> &'a str 表示返回值的生命周期与参数相同

实际效果:返回值的生命周期是 xy 生命周期中较短的那个

fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(&string1, &string2);
println!("最长的字符串是: {}", result); // OK
}
// println!("{}", result); // 错误!result 在这里无效
}

有时参数有不同的生命周期:

fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 只返回 x,所以返回值生命周期只与 x 相关
}

Rust 编译器可以在某些情况下推断生命周期,不需要显式标注。这些规则称为生命周期省略规则

规则 1:每个引用参数都有自己的生命周期

Section titled “规则 1:每个引用参数都有自己的生命周期”
fn foo(x: &str) -> ...
// 等价于
fn foo<'a>(x: &'a str) -> ...
fn foo(x: &str, y: &str) -> ...
// 等价于
fn foo<'a, 'b>(x: &'a str, y: &'b str) -> ...

规则 2:如果只有一个输入生命周期参数,它被赋给所有输出生命周期

Section titled “规则 2:如果只有一个输入生命周期参数,它被赋给所有输出生命周期”
fn foo(x: &str) -> &str
// 等价于
fn foo<'a>(x: &'a str) -> &'a str

规则 3:如果有 &self&mut selfself 的生命周期被赋给所有输出生命周期

Section titled “规则 3:如果有 &self 或 &mut self,self 的生命周期被赋给所有输出生命周期”
impl Foo {
fn method(&self, x: &str) -> &str
// 等价于
fn method<'a, 'b>(&'a self, x: &'b str) -> &'a str
}
// 不需要标注(规则 2)
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
// 需要标注(规则无法确定)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

当结构体持有引用时,必须标注生命周期:

struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
}

这个标注意味着:ImportantExcerpt 实例的生命周期不能超过它持有的引用 part

impl<'a> ImportantExcerpt<'a> {
// 规则 3:返回值生命周期与 self 相同
fn level(&self) -> i32 {
3
}
// 规则 3:返回值生命周期与 self 相同
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part
}
}

'static 是一个特殊的生命周期,表示引用在整个程序运行期间都有效:

let s: &'static str = "我有静态生命周期";

所有字符串字面量都有 'static 生命周期,因为它们被硬编码在程序的二进制文件中。

不要轻易使用 'static,大多数情况下编译器提示需要 'static 是因为:

  • 试图创建悬垂引用
  • 生命周期不匹配

先检查这些问题,而不是直接加 'static

三者可以结合使用:

use std::fmt::Display;
fn longest_with_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("公告: {}", ann);
if x.len() > y.len() { x } else { y }
}
fn first<'a>(x: &'a str, _y: &str) -> &'a str {
x
}

模式 2:返回新创建的数据(不需要生命周期)

Section titled “模式 2:返回新创建的数据(不需要生命周期)”
fn create_string() -> String {
String::from("hello")
}
// 错误!不能返回局部变量的引用
fn invalid<'a>() -> &'a str {
let s = String::from("hello");
// &s // 错误!s 在函数结束时被释放
"hello" // 这样可以,字符串字面量是 'static
}

以下代码无法编译,请添加正确的生命周期标注:

fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world!");
let result = longest(&s1, &s2);
println!("最长的是: {}", result);
}

解释为什么以下函数不需要生命周期标注:

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}

提示:参考生命周期省略规则。

创建一个结构体 Excerpt,持有一个字符串切片,正确标注生命周期:

// 定义结构体
fn main() {
let text = String::from("The quick brown fox jumps over the lazy dog.");
let first_part = text.split('.').next().unwrap();
let excerpt = /* 创建实例 */;
println!("{}", excerpt./* 字段名 */);
}

以下代码能否编译?为什么?

fn main() {
let string1 = String::from("xyz");
let result;
{
let string2 = String::from("long string is long");
result = longest(&string1, &string2);
}
println!("最长的字符串是: {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

思考

  • result 的生命周期是什么?
  • 为什么编译器会报错(或不报错)?

掌握了生命周期后,下一章我们将学习 Rust 中常用的数据结构。