长安的 Rust 入门教程长安的 Rust 入门教程
首页
基础教程
进阶内容
Rust 官网
编程指南
首页
基础教程
进阶内容
Rust 官网
编程指南
  • 进阶内容

    • 🚀 进阶内容
    • 第1章 - 泛型
    • 第2章 - Trait
    • 第3章 - 生命周期
    • 第4章 - 智能指针
    • 第5章 - 并发编程

第3章 - 生命周期

嗨,朋友!我是长安。

欢迎来到进阶教程最有挑战性的一章——生命周期(Lifetimes)!

老实说,生命周期是 Rust 中最难理解的概念之一。但别担心,我会用最简单的方式给你讲清楚。理解了生命周期,你对 Rust 的理解就会上一个新台阶!

🤔 什么是生命周期?

还记得我们学过的引用吗?引用就像是借别人的东西用一下。

生命周期就是告诉 Rust 编译器:"这个引用可以活多久"。

想象一下,你借了朋友的一本书:

  • 如果朋友还没还书就搬家了,那这本书就不存在了,你的借书卡就无效了
  • 这就是悬垂引用(dangling reference)的问题

Rust 的生命周期系统就是为了在编译时防止这种情况!

长安说

生命周期不是一个新的运行时特性,而是编译器用来验证你的代码是否安全的工具。

大多数情况下,Rust 能自动推断生命周期,你不需要显式标注。只有在编译器无法确定时,才需要你手动标注。

💔 悬垂引用问题

先看一个会出错的例子:

fn main() {
    let r;
    
    {
        let x = 5;
        r = &x;  // ❌ 错误!x 即将离开作用域
    }  // x 在这里被清理
    
    println!("r: {}", r);  // r 引用的 x 已经不存在了!
}

编译器会报错:

error[E0597]: `x` does not live long enough
  --> src/main.rs:6:13
   |
6  |         r = &x;
   |             ^^ borrowed value does not live long enough
7  |     }
   |     - `x` dropped here while still borrowed
8  |     
9  |     println!("r: {}", r);
   |                       - borrow later used here

编译器说:"x 活得不够久"!

📝 生命周期标注语法

生命周期参数用单引号 ' 开头,通常用小写字母,最常用的是 'a:

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

注意

'a 读作 "tick a" 或 "lifetime a"。它只是一个名字,你也可以用 'b、'c 或任何有意义的名字,比如 'input、'output。

🔗 函数中的生命周期

为什么需要标注?

看这个函数:

fn longest(x: &str, y: &str) -> &str {  // ❌ 编译错误
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

编译器不知道返回的是 x 还是 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 string2 = String::from("short");
    
    let result = longest(&string1, &string2);
    println!("最长的字符串: {}", result);
}

这里 <'a> 声明了一个生命周期参数。这个标注的意思是:

"返回的引用的生命周期与 x 和 y 中生命周期较短的那个一致。"

长安说

把 <'a> 理解为一个“合同”:

你告诉编译器:“参数 x 和 y 的生命周期至少要和返回值的生命周期一样长。”

生命周期有多长?

fn main() {
    let string1 = String::from("long string");
    
    {
        let string2 = String::from("short");
        let result = longest(&string1, &string2);
        println!("最长: {}", result);  // ✅ OK
    }  // string2 在这里被清理
}

但这样不行:

fn main() {
    let string1 = String::from("long string");
    let result;
    
    {
        let string2 = String::from("short");
        result = longest(&string1, &string2);
    }  // ❌ string2 被清理,但 result 可能引用它
    
    println!("result: {}", result);  // ❌ 错误!
}

因为 result 的甛命周期受到较短的 string2 限制,不能比 string2 活得更久。

🏗️ 结构体中的生命周期

如果结构体包含引用,必须标注生命周期:

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

fn main() {
    let title = String::from("《Rust 编程》");
    let author = String::from("长安");
    
    let book = Book {
        title: &title,
        author: &author,
    };
    
    println!("{} by {}", book.title, book.author);
}  // book、title、author 同时离开作用域

Book<'a> 表示:“Book 实例不能比它引用的数据活得更久。”

实例:文章预览

struct Article<'a> {
    content: &'a str,
}

impl<'a> Article<'a> {
    fn preview(&self, len: usize) -> &str {
        if self.content.len() <= len {
            self.content
        } else {
            &self.content[..len]
        }
    }
}

fn main() {
    let text = String::from("这是一篇很长的文章...");
    let article = Article { content: &text };
    
    println!("预览: {}", article.preview(10));
}

🧐 生命周期省略规则

Rust 有三个生命周期省略规则,让你在大多数情况下不需要显式标注:

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

fn first_word(s: &str) -> &str {  // 自动推断为 fn first_word<'a>(s: &'a str) -> &str
    // ...
}

规刱2:如果只有一个引用参数,返回值的生命周期与它相同

fn first_word(s: &str) -> &str {  // 自动推断为 fn first_word<'a>(s: &'a str) -> &'a str
    s.split_whitespace().next().unwrap_or("")
}

规刱3:如果是方法,返回值的生命周期与 self 相同

impl<'a> Book<'a> {
    fn get_title(&self) -> &str {  // 自动推断为 &'a str
        self.title
    }
}

长安说

这就是为什么你写了很多 Rust 代码都没遇到生命周期标注——因为编译器已经能自动推断了!

🔍 多个生命周期

有时候需要多个不同的生命周期:

fn longest_with_announcement<'a, 'b>(
    x: &'a str,
    y: &'a str,
    ann: &'b str,
) -> &'a str {
    println!("通知: {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

这里 'a 和 'b 是独立的生命周期,返回值只依赖于 'a。

🎉 静态生命周期

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

let s: &'static str = "我是静态字符串";  // 存储在程序二进制文件中

static HELLO: &str = "Hello, world!";  // 全局静态变量

注意

不要滥用 'static!大多数情况下,编译器提示你使用 'static 是因为你的代码有其他问题,不是真的需要静态生命周期。

🎯 实战例子:字符串分割器

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_delim = &self.remainder[..next_delim];
            self.remainder = &self.remainder[next_delim + self.delimiter.len()..];
            Some(until_delim)
        } else if !self.remainder.is_empty() {
            let rest = self.remainder;
            self.remainder = "";
            Some(rest)
        } else {
            None
        }
    }
}

fn main() {
    let text = "hello::world::rust";
    let split = StrSplit::new(text, "::");
    
    for word in split {
        println!("{}", word);
    }
}

💡 小结

在这一章,我们学会了:

  • 生命周期确保引用总是有效的
  • 使用 'a 语法标注生命周期
  • 函数中的生命周期:连接参数和返回值
  • 结构体中的生命周期:确保结构体不比引用活得更久
  • 生命周期省略规则:大多数情况下自动推断
  • 'static 生命周期:整个程序运行期间

核心要点:

  • 生命周期是编译时检查,不影响运行时性能
  • <'a> 是泛型参数,和 <T> 类似
  • 生命周期不改变引用的长度,只是描述关系
  • 大多数情况编译器能自动推断

长安的建议

生命周期确实很难,但不用担心:

  1. 大多数时候不需要:编译器能自动推断
  2. 遇到错误再学:等编译器提示你加生命周期时,再仔细理解
  3. 重点是理解原理:为什么需要生命周期(防止悬垂引用)
  4. 多写代码:理论不如实践,多写就会了

记住:生命周期标注不是在改变引用的生命周期,而是在告诉编译器不同引用之间的关系!

🚀 下一步

恭喜你掌握了生命周期!这是 Rust 最难的部分之一,能学到这里已经非常不容易了!

下一章,我们会学习 智能指针,它们是 Rust 中一些有特殊能力的指针类型。

第4章 - 智能指针 →

💪 练习题

  1. 修复这个函数,添加必要的生命周期标注:
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}
  1. 定义一个结构体 ImportantExcerpt,存储一个字符串引用,并为它实现一个方法返回这个引用

  2. 写一个函数,接受两个字符串引用和一个通知字符串,返回较短的字符串

答案示例
// 练习1 - 实际上不需要标注,编译器自动推断
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// 如果显式标注:
fn first_word<'a>(s: &'a str) -> &'a str {
    s.split_whitespace().next().unwrap_or("")
}

// 练习2
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn get_part(&self) -> &str {
        self.part
    }
}

// 练习3
fn shorter<'a, 'b>(x: &'a str, y: &'a str, ann: &'b str) -> &'a str {
    println!("通知: {}", ann);
    if x.len() < y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let excerpt = ImportantExcerpt {
        part: "Call me Ishmael.",
    };
    println!("{}", excerpt.get_part());
    
    let s1 = "hello";
    let s2 = "world!";
    let result = shorter(s1, s2, "Comparing");
    println!("Shorter: {}", result);
}
最近更新: 2025/12/26 18:01
Contributors: 王长安
Prev
第2章 - Trait
Next
第4章 - 智能指针