Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English Original

生命周期:证明引用的有效性

你将学到什么: 为什么生命周期会存在(编译器必须拿到安全性证明)、生命周期标注语法 ('a)、省略规则(为什么你经常不需要手写它们),以及结构体中的生命周期。

难度: 高级

C# 开发者通常不会思考“引用的生命周期”这个问题,因为垃圾回收器 (GC) 会负责对象的可达性。而在 Rust 中,编译器必须拿到证明,确认每个引用在被使用期间都始终有效。生命周期就是这份证明。


为什么生命周期会存在

考虑一个接收两个引用并返回其中之一的函数:

#![allow(unused)]
fn main() {
fn longest(a: &str, b: &str) -> &str {
    if a.len() > b.len() { a } else { b }
}
}

编译器会拒绝这段代码,因为它不知道返回的引用到底是借用了 a 还是 b。如果 b 离开了作用域,而调用者仍在尝试使用结果,就会产生悬垂指针。


生命周期标注语法

你使用 'a 语法来告诉编译器:“返回值的生命周期至少与输入变量们一样长。”

#![allow(unused)]
fn main() {
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}
}

重要提示: 生命周期标注不会改变值的存活时间。它们仅仅是在描述不同引用之间的存活关系,以便编译器进行校验。


生命周期省略规则

在大多数情况下,你并不需要手写 'a。编译器会自动应用三条规则来进行自动推断:

  1. 每一个输入引用都会获得其独立的生命周期。
  2. 如果只有一个输入引用,那么它对应的生命周期会被赋给所有的输出。
  3. 如果输入中包含 &self&mut self,那么该生命周期会被赋给所有的输出。
#![allow(unused)]
fn main() {
// 编译器会自动将这段代码:
fn first_word(s: &str) -> &str { ... }

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

结构体中的生命周期

如果一个结构体中包含引用,那么它必须带有生命周期标注。这确保了结构体本身的存活时间不能超过它所引用的数据。

#![allow(unused)]
fn main() {
struct Excerpt<'a> {
    text: &'a str, 
}

let novel = String::from("请叫我以实玛利。");
let first_sentence = Excerpt { text: &novel }; 
// 如果 'novel' 被释放了,'first_sentence' 也不能再继续存在。
}

'static 生命周期

'static 被称为静态生命周期,意味着引用能够在程序的整个运行期间一直存活。

  • 字符串字面量"Hello" 永远都是 &'static str,因为它被直接编码进了程序的二进制文件里。
  • 全局常量:通常也是 'static

C# 开发者总结

概念C# 对应物Rust 现实
对象寿命由 GC 动态管理由作用域/所有权静态定义
引用跟踪运行期 (是否可达?)编译期 (是否 Life’a?)
局部变量引用不允许或通过 Box 安全化被借用检查器严格限制
包含引用的结构体对 Class 而言没法直接实现需要显式标注 <'a>

练习:添加生命周期标注

挑战: 为一个涉及多个引用的结构体和函数补全生命周期标注。

#![allow(unused)]
fn main() {
struct Profile<'a> {
    username: &'a str,
}

fn get_username<'a>(p: &'a Profile) -> &'a str {
    p.username
}
}

关键理解: 虽然省略规则能处理很多简单场景,但在构建为了追求极致性能而频繁借用数据的复杂数据结构时,深入理解 'a 是必不可少的。