跳到主要内容

生命周期

生命周期是 Rust 确保引用始终有效的机制。它让编译器能够在编译时检查引用是否有效,避免悬垂引用。

什么是生命周期?

问题引入

考虑以下代码:

fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+

问题x 在内部作用域结束时被释放,但 r 仍然引用它,这会导致悬垂引用。

Rust 编译器会拒绝这段代码,因为它检测到 r 的生命周期比 x 长。

生命周期标注

生命周期标注不会改变引用的生命周期长度,它们只是描述多个引用之间的关系:

&i32        // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

函数中的生命周期

基本示例

// 错误示例:编译器不知道返回的引用来自哪个参数
// fn longest(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// }

// 正确示例:添加生命周期标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

fn main() {
let string1 = String::from("长字符串");

{
let string2 = String::from("xyz");
let result = longest(&string1, &string2);
println!("最长的字符串: {}", result);
}
}

解释

  • 'a 是生命周期参数
  • x: &'a str 表示 x 的生命周期至少为 'a
  • y: &'a str 表示 y 的生命周期至少为 'a
  • -> &'a str 表示返回值的生命周期也为 'a
  • 返回值的生命周期与参数中较短的那个一致

生命周期规则

编译器使用三条规则推断生命周期:

  1. 每个引用参数都获得一个生命周期

    • fn foo(x: &i32)fn foo<'a>(x: &'a i32)
  2. 如果只有一个输入生命周期,它被赋给所有输出生命周期

    • fn foo(x: &str) -> &strfn foo<'a>(x: &'a str) -> &'a str
  3. 如果有多个输入生命周期但其中一个是 &self 或 &mut self,self 的生命周期被赋给所有输出生命周期

    • 方法中的常见情况

示例分析

// 规则 1 和 2 生效:无需显式标注
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

// 需要显式标注:多个输入生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

// 返回值不一定来自参数
fn longest_x<'a>(x: &'a str, y: &str) -> &'a str {
x // 返回值只与 x 相关
}

// 错误:不能返回局部变量的引用
// fn invalid<'a>(x: &str, y: &str) -> &'a str {
// let result = String::from("hello");
// &result // result 在函数结束时被释放
// }

结构体中的生命周期

基本用法

// 结构体持有引用时需要生命周期标注
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: &'a str 表示引用的生命周期必须至少与结构体一样长
  • 结构体实例不能比它持有的引用存活更久

结构体方法

struct ImportantExcerpt<'a> {
part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
// 规则 3 生效:self 的生命周期传递给返回值
fn level(&self) -> i32 {
3
}

// 返回值生命周期与 self 相同
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part
}

// 多生命周期示例
fn compare_parts<'b>(&self, other: &'b str) -> &'a str {
if self.part.len() > other.len() {
self.part
} else {
self.part // 返回 self.part,生命周期为 'a
}
}
}

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.level());
println!("部分: {}", excerpt.announce_and_return_part("公告"));
}

多个生命周期

struct Context<'s, 'c> {
text: &'s str,
comment: &'c str,
}

impl<'s, 'c> Context<'s, 'c> {
fn get_text(&self) -> &'s str {
self.text
}

fn get_comment(&self) -> &'c str {
self.comment
}
}

fn main() {
let text = String::from("主要内容");
{
let comment = String::from("这是一条评论");
let ctx = Context {
text: &text,
comment: &comment,
};
println!("{}", ctx.get_text());
println!("{}", ctx.get_comment());
}
}

生命周期省略

历史上,所有引用都需要显式生命周期标注。现在,编译器可以自动推断常见模式:

// 省略前
fn first_word<'a>(s: &'a str) -> &'a str {
// ...
}

// 省略后
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

// 另一个省略示例
fn longest_string(x: &str, y: &str) -> String {
if x.len() > y.len() {
x.to_string()
} else {
y.to_string()
}
}

静态生命周期

'static 表示整个程序运行期间都有效的生命周期:

// 字符串字面量具有 'static 生命周期
let s: &'static str = "我有静态生命周期";

// 可以作为默认生命周期
fn print_str(s: &'static str) {
println!("{}", s);
}

fn main() {
let s1: &'static str = "hello";
print_str(s1);

// 错误:String 不能转换为 &'static str
// let s2 = String::from("world");
// print_str(&s2);
}

注意

  • 不要轻易使用 'static 来解决生命周期问题
  • 它通常用于:
    • 字符串字面量
    • 全局数据
    • 线程间共享的数据

生命周期子类型化

生命周期可以有包含关系:

// 'a: 'b 表示 'a 至少和 'b 一样长
fn choose_first<'a, 'b>(first: &'a i32, second: &'b i32) -> &'a i32
where
'a: 'b, // 'a 包含 'b
{
first
}

fn main() {
let first = 10; // 'a
let longer;
{
let second = 20; // 'b
longer = choose_first(&first, &second);
println!("{}", longer);
}
println!("{}", longer);
}

协变和逆变

生命周期具有协变性:

fn main() {
// 长生命周期可以赋值给短生命周期
let x: &'static str = "hello";
let y: &str = x; // 'static 可以转换为任意 'a

// 但反过来不行
// let z: &'static str = y; // 错误!
}

实际应用示例

解析器

struct Parser<'a> {
input: &'a str,
}

impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input }
}

fn peek(&self) -> Option<char> {
self.input.chars().next()
}

fn consume(&mut self) -> Option<char> {
let ch = self.input.chars().next()?;
self.input = &self.input[ch.len_utf8()..];
Some(ch)
}

fn parse_identifier(&mut self) -> &'a str {
let start = self.input;
while let Some(ch) = self.peek() {
if ch.is_alphanumeric() || ch == '_' {
self.consume();
} else {
break;
}
}
&start[..start.len() - self.input.len()]
}
}

fn main() {
let code = "hello_world = 42";
let mut parser = Parser::new(code);

let ident = parser.parse_identifier();
println!("标识符: {}", ident);
}

文本处理

struct TextProcessor<'a> {
text: &'a str,
words: Vec<&'a str>,
}

impl<'a> TextProcessor<'a> {
fn new(text: &'a str) -> Self {
let words: Vec<&str> = text.split_whitespace().collect();
TextProcessor { text, words }
}

fn find_word(&self, word: &str) -> Option<&'a str> {
self.words.iter().find(|&&w| w == word).copied()
}

fn all_words(&self) -> &[&'a str] {
&self.words
}
}

fn main() {
let text = "Rust 是一门系统编程语言";
let processor = TextProcessor::new(text);

if let Some(word) = processor.find_word("Rust") {
println!("找到: {}", word);
}

println!("所有单词: {:?}", processor.all_words());
}

常见错误和解决方案

错误 1:生命周期不匹配

// 错误
fn bad_example<'a>(x: &str, y: &str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

// 正确
fn good_example<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

错误 2:返回局部引用

// 错误
fn create_string() -> &str {
let s = String::from("hello");
&s // s 在函数结束时被释放
}

// 正确方案 1:返回所有权
fn create_string_owned() -> String {
String::from("hello")
}

// 正确方案 2:传入可变引用
fn create_string_to<'a>(output: &'a mut String) -> &'a str {
*output = String::from("hello");
output
}

错误 3:结构体生命周期

// 错误:结构体比引用活得更长
fn bad_struct() {
let excerpt;
{
let text = String::from("hello");
excerpt = ImportantExcerpt { part: &text };
} // text 被释放
// println!("{}", excerpt.part); // 悬垂引用!
}

// 正确:确保引用比结构体活得长
fn good_struct() {
let text = String::from("hello");
let excerpt = ImportantExcerpt { part: &text };
println!("{}", excerpt.part);
}

小结

本章我们学习了:

  1. 生命周期概念:确保引用始终有效
  2. 生命周期标注'a 语法和用途
  3. 函数生命周期:参数和返回值的关联
  4. 结构体生命周期:持有引用的结构体
  5. 生命周期省略:编译器自动推断规则
  6. 静态生命周期'static 的含义和用途

练习

  1. 编写一个函数,接受两个字符串切片,返回其中较长的那个
  2. 创建一个结构体,存储字符串切片,并实现相关方法
  3. 解释为什么以下代码无法编译:
    fn main() {
    let r;
    {
    let x = 5;
    r = &x;
    }
    println!("{}", r);
    }
  4. 实现一个简单的文本解析器,使用生命周期确保安全性