引用有效期:生命周期入门

原理

生命周期(lifetime)是编译器对引用有效范围的静态标注,本质是 MIR 层的数据流分析结果。每一个引用均有一个隐式生命周期变量,编译器通过 borrow checker 求解生命周期的包含关系,确保所有引用始终指向有效数据。

'a 不会延长或缩短任何实际对象的存活时间(代码生成阶段完全消除),它只是一个编译期的”约束关系”。'static 生命周期表示数据在整个进程生命周期内有效(存放于只读数据段 .rodata),字符串字面量 "hello"&'static str

三条省略规则基于 Rust 团队对常见模式的统计分析设计,覆盖 90%+ 的场景:

  1. 每个引用参数获得独立生命周期
  2. 单个输入生命周期 → 返回值和它绑定
  3. &self / &mut self → 返回值和 self 绑定

违反规则时编译器在精确的工作点报错(NLL borrowck),而非泛泛的”生命周期错误”。

C: 悬垂指针问题


语法

悬垂引用(编译器阻止)

// let r;
// {
//     let x = 5;
//     r = &x;
// }
// println!("{}", r);  // 编译错误:x does not live long enough

函数中的生命周期标注

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 语义:返回值生命周期 = min(生命周期(x), 生命周期(y))
fn first<'a>(x: &'a str, _y: &str) -> &'a str { x }
// y 可以不和返回值绑定

结构体中的生命周期

struct Excerpt<'a> {
    part: &'a str,  // 字段引用需标注
}
 
impl<'a> Excerpt<'a> {
    fn get(&self) -> &str { self.part }  // 由规则3自动推断
}

省略规则速查

场景需要标注?
fn f(s: &str) -> &str否(规则1+2)
fn f(&self, s: &str) -> &str否(规则1+3)
fn f(x: &str, y: &str) -> &str是(多输入,编译器无法确定)
struct S { r: &str }
fn f() -> &str通常不合理(悬垂风险)

静态生命周期

let s: &'static str = "hello";  // 字符串字面量

实践

力扣问题

力扣: 力扣字符串切片操作题 — 字符串切片操作

fn find_lcs<'a>(a: &'a str, b: &'a str) -> usize {
    let a: Vec<char> = a.chars().collect();
    let b: Vec<char> = b.chars().collect();
    // LCS 算法
    0
}

AI 自检

  1. 'a 标注在编译后会保留在二进制文件中吗?生命周期信息在哪个编译阶段被擦除?
  2. NLL(Non-Lexical Lifetimes)解决了传统生命周期分析的什么问题?