7.1 为什么需要生命周期标注

7.1.1 悬垂引用问题

fn main() {
    let r;
    
    {
        let x = 5;
        r = &x; // ❌ 错误!x 的生命周期太短
    } // x 在此离开作用域
    
    println!("r: {}", r); // r 引用的数据已失效
}

编译器报错

error[E0597]: `x` does not live long enough

7.1.2 生命周期的本质

生命周期不是延长变量的寿命,而是描述引用之间的关系。

类比:合同有效期

  • 你借了一本书(引用)
  • 合同规定归还日期(生命周期)
  • 合同不会让书存在更久,只是规定你能用多久

7.1.3 借用检查器

Rust 编译器的借用检查器会比较作用域来确保引用有效。

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

'b'a 短,所以引用无效。


7.2 生命周期标注语法

7.2.1 语法规则

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

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

常见的生命周期名称

  • 'a, 'b, 'c - 通用生命周期
  • 'static - 特殊的静态生命周期

7.2.2 单个生命周期标注没有意义

fn foo<'a>(x: &'a i32) {
    // 单个生命周期标注不提供额外信息
}

生命周期标注的意义在于描述多个引用之间的关系


7.3 函数签名中的生命周期

7.3.1 问题场景

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

编译器报错

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter

问题:编译器不知道返回的引用是 x 还是 y,无法确定返回值的生命周期。

7.3.2 添加生命周期标注

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("long string is long");
    let string2 = String::from("xyz");
    
    let result = longest(string1.as_str(), string2.as_str());
    println!("最长的字符串是 {}", result);
}

含义

  • 参数 xy 的生命周期都是 'a
  • 返回值的生命周期也是 'a
  • 'a 的实际生命周期是 xy 生命周期中较短的那个

7.3.3 生命周期约束示例

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("long string is long");
    
    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("最长的字符串是 {}", result); // ✅ 正确
    }
}

错误示例

fn main() {
    let string1 = String::from("long string is long");
    let result;
    
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    } // string2 在此离开作用域
    
    println!("最长的字符串是 {}", result); // ❌ 错误!
}

7.3.4 返回值不依赖参数的情况

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str() // ❌ 错误!返回局部变量的引用
}

正确做法:返回所有权

fn longest(x: &str, y: &str) -> String {
    String::from("really long string")
}

7.4 结构体中的生命周期

7.4.1 结构体持有引用

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("Could not find a '.'");
    
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    
    println!("摘录:{}", i.part);
}

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

7.4.2 错误示例

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

fn main() {
    let i;
    {
        let novel = String::from("Call me Ishmael.");
        let first_sentence = novel.split('.').next().unwrap();
        i = ImportantExcerpt {
            part: first_sentence,
        };
    } // novel 在此离开作用域
    
    println!("{}", i.part); // ❌ 错误!
}

7.4.3 结构体方法中的生命周期

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

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
    
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("注意!{}", announcement);
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    
    println!("级别:{}", i.level());
    println!("部分:{}", i.announce_and_return_part("听好了"));
}

7.5 生命周期省略规则(Elision Rules)

编译器使用三条规则来推断生命周期,如果应用这些规则后仍有歧义,则需要显式标注。

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

fn foo(x: &i32) // 推断为 fn foo<'a>(x: &'a i32)
fn foo(x: &i32, y: &i32) // 推断为 fn foo<'a, 'b>(x: &'a i32, y: &'b i32)

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

fn foo(x: &i32) -> &i32 // 推断为 fn foo<'a>(x: &'a i32) -> &'a i32

7.5.3 规则 3:如果有多个输入生命周期,但其中一个是 &self 或 &mut self,self 的生命周期赋给所有输出生命周期

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        // 推断为:
        // fn announce_and_return_part<'a, 'b>(&'a self, announcement: &'b str) -> &'a str
        self.part
    }
}

7.5.4 需要显式标注的情况

// 规则无法推断,需要显式标注
fn longest(x: &str, y: &str) -> &str { // ❌ 错误
    // ...
}

// 显式标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    // ...
}

7.6 'static 生命周期

'static 表示引用在整个程序运行期间都有效。

7.6.1 字符串字面量

fn main() {
    let s: &'static str = "I have a static lifetime.";
    println!("{}", s);
}

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

7.6.2 静态变量

static LANGUAGE: &str = "Rust";

fn main() {
    println!("语言:{}", LANGUAGE);
}

7.6.3 何时使用 'static

不要滥用 'static

// ❌ 不好的做法
fn get_string() -> &'static str {
    let s = String::from("hello");
    // s.as_str() // 错误!不能返回局部变量的引用
    "hello" // 只能返回字符串字面量
}

// ✅ 好的做法
fn get_string() -> String {
    String::from("hello")
}

7.7 泛型 + Trait + 生命周期的综合运用

7.7.1 综合示例

use std::fmt::Display;

fn longest_with_an_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 main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";
    
    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "今天是特别的一天",
    );
    
    println!("最长的字符串是 {}", result);
}

解析

  • 'a:生命周期参数
  • T:泛型类型参数
  • T: Display:Trait Bound

7.7.2 复杂示例

use std::fmt::Display;

struct Pair<'a, T> {
    x: &'a T,
    y: &'a T,
}

impl<'a, T> Pair<'a, T> {
    fn new(x: &'a T, y: &'a T) -> Self {
        Self { x, y }
    }
}

impl<'a, T: Display + PartialOrd> Pair<'a, T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("最大值是 x = {}", self.x);
        } else {
            println!("最大值是 y = {}", self.y);
        }
    }
}

fn main() {
    let x = 10;
    let y = 20;
    
    let pair = Pair::new(&x, &y);
    pair.cmp_display();
}

7.8 实战:实现一个泛型缓存结构

项目需求

  1. 缓存计算结果
  2. 支持泛型类型
  3. 使用生命周期确保安全

实现代码

use std::collections::HashMap;
use std::hash::Hash;

struct Cacher<'a, T, U>
where
    T: Fn(&U) -> U,
    U: Clone + Hash + Eq,
{
    calculation: T,
    values: HashMap<&'a U, U>,
}

impl<'a, T, U> Cacher<'a, T, U>
where
    T: Fn(&U) -> U,
    U: Clone + Hash + Eq,
{
    fn new(calculation: T) -> Cacher<'a, T, U> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }
    
    fn value(&mut self, arg: &'a U) -> U {
        match self.values.get(arg) {
            Some(v) => v.clone(),
            None => {
                let v = (self.calculation)(arg);
                self.values.insert(arg, v.clone());
                v
            }
        }
    }
}

fn main() {
    let mut expensive_closure = Cacher::new(|num: &u32| {
        println!("计算中...");
        std::thread::sleep(std::time::Duration::from_secs(1));
        num * 2
    });
    
    let arg1 = 10;
    let arg2 = 20;
    
    println!("第一次调用:{}", expensive_closure.value(&arg1));
    println!("第二次调用(缓存):{}", expensive_closure.value(&arg1));
    println!("不同参数:{}", expensive_closure.value(&arg2));
}

简化版本(不使用生命周期)

use std::collections::HashMap;

struct Cacher<T, U>
where
    T: Fn(U) -> U,
    U: Clone + Hash + Eq,
{
    calculation: T,
    values: HashMap<U, U>,
}

impl<T, U> Cacher<T, U>
where
    T: Fn(U) -> U,
    U: Clone + Hash + Eq,
{
    fn new(calculation: T) -> Cacher<T, U> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }
    
    fn value(&mut self, arg: U) -> U {
        match self.values.get(&arg) {
            Some(v) => v.clone(),
            None => {
                let v = (self.calculation)(arg.clone());
                self.values.insert(arg, v.clone());
                v
            }
        }
    }
}

fn main() {
    let mut expensive_closure = Cacher::new(|num: u32| {
        println!("计算中...");
        std::thread::sleep(std::time::Duration::from_secs(1));
        num * 2
    });
    
    println!("第一次调用:{}", expensive_closure.value(10));
    println!("第二次调用(缓存):{}", expensive_closure.value(10));
    println!("不同参数:{}", expensive_closure.value(20));
}

常见误区与陷阱

误区 1:以为生命周期会改变引用的实际生命周期

// ❌ 错误理解
// 生命周期标注会让 x 活得更久

// ✅ 正确理解
// 生命周期标注只是告诉编译器引用之间的关系
// 不会改变任何值的实际生命周期

误区 2:过度使用 'static

// ❌ 不好
fn get_str() -> &'static str {
    // 只能返回字符串字面量
    "hello"
}

// ✅ 更好
fn get_str() -> String {
    String::from("hello")
}

误区 3:混淆生命周期和作用域

// 生命周期:引用有效的时间范围
// 作用域:变量存在的代码范围

fn main() {
    let r;                // r 的作用域开始
    {
        let x = 5;        // x 的作用域开始
        r = &x;           // r 引用 x
    }                     // x 的作用域结束,x 被释放
                          // r 的生命周期在此失效(引用的数据已释放)
    // println!("{}", r); // 错误!
}                         // r 的作用域结束

实战练习

练习 7.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
    }
}

fn main() {
    let string1 = String::from("long string");
    let result;
    
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
        println!("最长的字符串是:{}", result);
    }
}

输出

最长的字符串是:long string

说明:编译器无法确定返回值引用的是 x 还是 y,因此需要生命周期标注 'a 来告诉编译器:返回值的生命周期是两个参数生命周期中较短的那个。


练习 7.2:结构体生命周期

定义一个结构体 Book,包含标题和作者的引用,实现方法返回完整信息。

参考答案

struct Book<'a> {
    title: &'a str,
    author: &'a str,
}

impl<'a> Book<'a> {
    fn new(title: &'a str, author: &'a str) -> Book<'a> {
        Book { title, author }
    }
    
    fn info(&self) -> String {
        format!("《{}》 —— {}", self.title, self.author)
    }
    
    fn title(&self) -> &str {
        self.title
    }
}

fn main() {
    let title = String::from("Rust 程序设计");
    let author = String::from("Steve Klabnik");
    
    let book = Book::new(&title, &author);
    println!("{}", book.info());
    println!("书名:{}", book.title());
}

输出

《Rust 程序设计》 —— Steve Klabnik
书名:Rust 程序设计

说明Book 结构体持有引用,生命周期参数 'a 确保结构体实例不会比它引用的数据活得更久。


练习 7.3:多个生命周期

编写函数,接受两个不同生命周期的引用,返回其中一个。

参考答案

fn first_or_default<'a, 'b>(first: &'a str, default: &'b str) -> &'a str
where
    'b: 'a,
{
    if first.is_empty() {
        default
    } else {
        first
    }
}

fn main() {
    let default = "默认值";
    
    let value1 = String::from("hello");
    let result1 = first_or_default(&value1, default);
    println!("result1: {}", result1);
    
    let value2 = String::from("");
    let result2 = first_or_default(&value2, default);
    println!("result2: {}", result2);
}

输出

result1: hello
result2: 默认值

说明'b: 'a 表示 'b 至少和 'a 一样长。这样当 first 为空时,返回 default 是安全的,因为 default 的生命周期不短于返回值要求的生命周期。


练习 7.4:综合应用

实现一个泛型结构体,持有引用,并实现带有 Trait Bound 的方法。

参考答案

use std::fmt::Display;

struct Wrapper<'a, T: Display> {
    value: &'a T,
    label: &'a str,
}

impl<'a, T: Display> Wrapper<'a, T> {
    fn new(value: &'a T, label: &'a str) -> Wrapper<'a, T> {
        Wrapper { value, label }
    }
    
    fn display(&self) {
        println!("[{}]: {}", self.label, self.value);
    }
}

impl<'a, T: Display + PartialOrd> Wrapper<'a, T> {
    fn is_greater_than(&self, other: &T) -> bool {
        self.value > other
    }
}

fn main() {
    let num = 42;
    let w1 = Wrapper::new(&num, "数字");
    w1.display();
    println!("42 > 30? {}", w1.is_greater_than(&30));
    
    let text = String::from("hello");
    let w2 = Wrapper::new(&text, "文本");
    w2.display();
    
    let pi = 3.14;
    let w3 = Wrapper::new(&pi, "圆周率");
    w3.display();
    println!("3.14 > 3.0? {}", w3.is_greater_than(&3.0));
}

输出

[数字]: 42
42 > 30? true
[文本]: hello
[圆周率]: 3.14
3.14 > 3.0? true

说明:这个例子综合运用了泛型参数 T、生命周期参数 'a、Trait Bound(DisplayPartialOrd),以及有条件地为特定类型实现方法。


本章小结

  1. 生命周期的本质:描述引用之间的关系,不改变实际生命周期
  2. 生命周期标注:使用 'a 等符号标注引用的生命周期参数
  3. 函数签名:当返回引用时,需要标注生命周期关系
  4. 结构体:持有引用的结构体需要生命周期参数
  5. 省略规则:编译器可以推断大部分情况,减少显式标注
  6. 'static:整个程序运行期间有效,不要滥用
  7. 综合运用:生命周期、泛型、Trait 可以组合使用
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐