生命周期

生命周期(Lifetime)是 Rust 中引用保持有效的作用域。

为什么需要生命周期?

生命周期防止悬垂引用(dangling references):

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

错误原因x 的生命周期 'b 比 r 的生命周期 'a 短,当尝试使用 r 时,x 已被释放。

生命周期作用域可视化

fn main() {
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("{}", r);    //          |
}                         // ---------+
'a 生命周期
整个函数作用域
'b 生命周期
内部块作用域
生命周期 'a (变量 r)
开始结束
从第 2 行到函数结束(第 9 行)
生命周期 'b (变量 x)
开始结束
从第 5 行到第 7 行(块结束)
⚠️
编译错误!
变量 r 引用了 x, 但 x 的生命周期 'b 比 r 的生命周期 'a 短。 当执行到第 8 行时,x 已被释放,r 变成了悬垂引用。
💡
解决方案
x 的声明移到外层作用域, 确保其生命周期至少和 r 一样长。
💡 悬停在时间线上查看生命周期范围

生命周期注解语法

生命周期注解使用单引号开头,通常使用小写字母:

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

函数中的生命周期

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("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("最长的字符串是: {}", result);
}

含义:返回值的生命周期与参数中生命周期较短的那个相同。

Loading diagram...

生命周期省略规则

编译器可以在某些情况下自动推导生命周期,无需显式标注。

三条省略规则

  1. 规则 1:每个引用参数都有自己的生命周期
fn foo(x: &i32)  // 实际是 fn foo<'a>(x: &'a i32)
  1. 规则 2:如果只有一个输入生命周期,赋予所有输出
fn foo(x: &i32) -> &i32  // 实际是 fn foo<'a>(x: &'a i32) -> &'a i32
  1. 规则 3:如果有 &self&mut self,返回值的生命周期是 self 的生命周期
impl MyStruct {
    fn get_data(&self) -> &str {  // 返回值生命周期等于 self
        &self.data
    }
}

需要显式标注的情况

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 }
}

结构体中的生命周期

结构体包含引用时,需要生命周期注解:

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

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("找不到 '.'");

    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };

    println!("{}", excerpt.part);
}

含义ImportantExcerpt 实例的生命周期不能超过 part 字段引用的数据。

方法中的生命周期

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {  // 省略规则 3:返回值生命周期来自 self
        3
    }

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

静态生命周期

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

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

字符串字面量默认是 'static,因为它们被存储在程序的二进制文件中。

谨慎使用 'static

// ❌ 不推荐:过度使用 'static
fn get_str() -> &'static str {
    "hello"
}

// ✅ 更好:返回 String(有所有权)
fn get_string() -> String {
    String::from("hello")
}

生命周期约束

可以指定生命周期之间的关系:

fn longest_with_constraint<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
    // 'b: 'a 表示 'b 的生命周期至少与 'a 一样长
    x
}

多个生命周期参数

fn compare<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x  // 只能返回 x,不能返回 y(生命周期不同)
}

fn main() {
    let str1 = String::from("long string is long");
    let result;
    {
        let str2 = String::from("xyz");
        result = compare(str1.as_str(), str2.as_str());
    }  // str2 被释放,但不影响 result(它来自 str1)

    println!("{}", result);  // ✅ 正常工作
}

生命周期与泛型

结合泛型和生命周期:

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
    }
}

实践示例

示例 1:解析配置

struct Config<'a> {
    host: &'a str,
    port: u16,
}

impl<'a> Config<'a> {
    fn new(host: &'a str, port: u16) -> Self {
        Config { host, port }
    }

    fn connection_string(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

fn main() {
    let host = String::from("localhost");
    let config = Config::new(&host, 8080);

    println!("连接到: {}", config.connection_string());
}  // host 和 config 同时被释放,生命周期正确

示例 2:迭代器实现

struct StrSplit<'a> {
    remainder: &'a str,
    delimiter: &'a str,
}

impl<'a> StrSplit<'a> {
    fn new(haystack: &'a str, delimiter: &'a str) -> Self {
        StrSplit {
            remainder: haystack,
            delimiter,
        }
    }
}

impl<'a> Iterator for StrSplit<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(next_delim) = self.remainder.find(self.delimiter) {
            let until_delimiter = &self.remainder[..next_delim];
            self.remainder = &self.remainder[next_delim + self.delimiter.len()..];
            Some(until_delimiter)
        } else if self.remainder.is_empty() {
            None
        } else {
            let rest = self.remainder;
            self.remainder = "";
            Some(rest)
        }
    }
}

fn main() {
    let haystack = "a,b,c";
    let letters: Vec<_> = StrSplit::new(haystack, ",").collect();
    println!("{:?}", letters);  // ["a", "b", "c"]
}

常见错误与修复

错误 1:返回值生命周期不明确

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 }
}

错误 2:返回局部变量的引用

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

// 修复:返回 String(转移所有权)
fn get_string() -> String {  // ✅
    String::from("hello")
}

错误 3:结构体缺少生命周期注解

struct Excerpt {
    part: &str,  // ❌ 编译错误:缺少生命周期
}

// 修复:添加生命周期
struct Excerpt<'a> {
    part: &'a str,  // ✅
}

实践建议

✅ 推荐做法

// 1. 优先使用生命周期省略规则
fn first_word(s: &str) -> &str {  // 自动推导
    &s[..1]
}

// 2. 只在必要时显式标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// 3. 使用有意义的生命周期名称
fn parse_config<'cfg>(input: &'cfg str) -> Config<'cfg> {
    // 'cfg 比 'a 更具描述性
    Config { data: input }
}

struct Config<'cfg> {
    data: &'cfg str,
}

❌ 避免做法

// ❌ 过度使用 'static
fn get_data() -> &'static str {
    "data"  // 只有在确实是全局常量时才使用
}

// ❌ 不必要的复杂生命周期
fn simple<'a, 'b, 'c>(x: &'a str, y: &'b str) -> &'c str {
    // 如果可以简化,就简化
    x
}

// ❌ 返回局部变量的引用
fn bad() -> &str {
    let s = String::from("bad");
    &s  // 悬垂引用
}

练习题

练习 1:修复生命周期错误

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 }
}

练习 2:结构体生命周期

实现一个包含字符串切片的结构体:

struct Book {
    title: &str,
    author: &str,
}
查看答案
struct Book<'a> {
    title: &'a str,
    author: &'a str,
}

fn main() {
    let title = String::from("Rust Programming");
    let author = String::from("Graydon Hoare");

    let book = Book {
        title: &title,
        author: &author,
    };

    println!("{} by {}", book.title, book.author);
}

小结

  • 生命周期确保引用始终有效
  • ✅ 使用 'a 语法标注生命周期
  • ✅ 编译器可以通过省略规则自动推导生命周期
  • ✅ 结构体包含引用时需要生命周期注解
  • 'static 表示整个程序生命周期
  • ✅ 生命周期在编译时检查,零运行时开销

下一步,我们将学习模式匹配