生命周期标注语法
> 掌握生命周期标注的语法和方法,理解不同标注方式对函数行为的影响。
生命周期标注语法详解
从问题到解决方案
rust
▶ Run// 问题场景:返回两个字符串中较长的
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// ❌ 编译错误:
// error[E0106]: missing lifetime specifier
// --> src/main.rs:1:21
// |
// 1 | fn longest(x: &str, y: &str) -> &str {
// | - - ^ expected named lifetime parameter
// | | |
// | | let's call the lifetime of this reference `'1`
// | let's call the lifetime of this reference `'2`
// |
// = help: consider introducing a named lifetime parameter
// help: consider introducing a named lifetime parameter
// |
// 1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// | ++++ ++ ++ ++为什么需要生命周期标注?
编译器的困境:
x: &str ──┐
├─> 返回值可能来自 x 或 y
y: &str ──┘
问题:
• x 和 y 有不同的生命周期
• 编译器不知道返回的引用来自哪个参数
• 无法保证返回引用的安全性
┌───────────────────────────────────────────────────┐
│ 为什么不能自动推断? │
├───────────────────────────────────────────────────┤
│ │
│ x 的生命周期:━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ y 的生命周期:━━━━━━━━━━━━━━━━━ │
│ │
│ 返回值可能来自 x 或 y,编译器无法确定: │
│ • 如果返回 x:生命周期 = x 的生命周期 │
│ • 如果返回 y:生命周期 = y 的生命周期 │
│ • 运行时才能决定,编译时无法验证 │
│ │
└───────────────────────────────────────────────────┘基本语法
rust
▶ Run// 生命周期参数以撇号开头,通常小写
// 常见命名:'a, 'b, 'c, ...
// 带生命周期的引用
let r: &'a i32; // 不可变引用
let r: &'a mut i32; // 可变引用
// 多个生命周期
let r: &'a &'b i32; // 引用的引用
let r: &'a mut &'b i32; // 可变引用
// 生命周期参数的位置
fn function<'a>(param: &'a i32) { }
struct Struct<'a> { field: &'a str }
enum Enum<'a> { Variant(&'a str) }生命周期标注的位置
rust
▶ Run// 1. 函数参数和返回值
fn foo<'a>(x: &'a str) -> &'a str {
x
}
// 2. 结构体字段
struct Bar<'a> {
name: &'a str,
}
// 3. 枚举变体
enum Baz<'a> {
Name(&'a str),
Value { data: &'a str },
}
// 4. impl 块
impl<'a> Bar<'a> {
fn get(&self) -> &str {
self.name
}
}函数中的生命周期深度解析
longest 函数详细分析
rust
▶ Run// ✅ 正确:显式标注生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 'a 的含义:
// 1. x 和 y 都至少在生命周期 'a 内有效
// 2. 返回的引用也只在 'a 内有效
// 3. 调用者保证 x 和 y 都足够"长寿"内存布局图解:
场景 1:两个字符串生命周期相同
let s1 = String::from("hello"); // 's1 开始
let s2 = String::from("world"); // 's2 开始
let result = longest(&s1, &s2); // 'result 开始
println!("{}", result); // 使用 result
时间线:
┌────────────────────────────────────────────────┐
│ s1: "hello" │
│ s2: "world" (同时开始,同时结束) │
│ result ──────────────────────────────────┐ │
└────────────────────────────────────────────┼───┘
│
生命周期 'a = s1 和 s2 的交集 ───────────────┘
栈内存:
┌──────────────────┐
│ s1 ────────┐ │
│ ptr │ hello│
│ len │ 5 │
│ cap │ 5 │
└────────────┴──────┘
│ s2 ────────┐ │
│ ptr │ world│
│ len │ 5 │
│ cap │ 5 │
└────────────┴──────┘
│ result ────┐ │
│ ↓ │
│ 指向 s1 或 s2 │
└──────────────────┘
场景 2:两个字符串生命周期不同
let s1 = String::from("long string"); // 's1 开始
{
let s2 = String::from("short"); // 's2 开始
let result = longest(&s1, &s2); // 'result 开始
println!("{}", result); // ✅ result 在 s2 前失效
} // s2 和 result 失效
println!("{}", s1); // s1 仍有效
时间线:
┌────────────────────────────────────────────────┐
│ s1: "long string" │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ s2: "short" │
│ ━━━━━━━━━━━━━━━━━ │
│ ↑ │
│ result 的生命周期 │
│ (受限于 s2,较短者) │
└────────────────────────────────────────────────┘
生命周期约束:
'a = min('s1, 's2) = 's2
result 的生命周期不能超过 s2实际使用案例
rust
▶ Runfn main() {
let s1 = String::from("long string"); // 's1
let s2 = String::from("short"); // 's2
// 'a 是 's1 和 's2 的交集
let result = longest(&s1, &s2);
// result 的生命周期受限于 s1 和 s2 中较短的那个
println!("结果:{}", result);
} // result 先失效,然后 s2,最后 s1不同生命周期标注的影响
rust
▶ Run// 情况 1:返回与第一个参数相同生命周期
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 只返回 x,与 y 无关
}
fn main() {
let s1 = String::from("hello");
let result;
{
let s2 = String::from("world");
result = first(&s1, &s2);
// ✅ result 的生命周期 = s1 的生命周期
// 即使 s2 失效,result 仍然有效(因为来自 s1)
}
println!("{}", result); // ✅ 仍然有效
}
// 情况 2:返回与第二个参数相同生命周期
fn second<'a, 'b>(x: &'a str, y: &'b str) -> &'b str {
y // 只返回 y,与 x 无关
}
fn main() {
let result;
let s1 = String::from("hello");
{
let s2 = String::from("world");
result = second(&s1, &s2);
// ❌ result 的生命周期 = s2 的生命周期
}
// println!("{}", result); // 错误:s2 已失效
}
// 情况 3:返回受限于两者(最常见)
fn either<'a>(x: &'a str, y: &'a str) -> &'a str {
// 可能返回 x 或 y,所以返回受限于两者
if x.len() > y.len() {
x
} else {
y
}
}生命周期标注对比图:
函数签名对比:
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
││ │ │ │
││ │ │ └─ 返回 x 的生命周期
││ │ └────────────── y 有独立生命周期
││ └─────────────────────────── x 的生命周期
│└───────────────────────────────────── y 的生命周期参数
└───────────────────────────────────── x 的生命周期参数
内存关系:
x: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↑ 返回值
y: ━━━━━━━━━━━━━━━━━
(y 的生命周期不影响返回值)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
fn either<'a>(x: &'a str, y: &'a str) -> &'a str
│ │ │ │
│ │ │ └─ 返回值受限于两者
│ │ └────────────── y 必须至少活 'a
│ └─────────────────────────── x 必须至少活 'a
└───────────────────────────────────── 同一个生命周期参数
内存关系:
x: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↑
y: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↑ ↑
└─ 'a 开始 └─ 'a 结束(较短者决定)生命周期标注的含义
rust
▶ Runfn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 'a 的含义:
// 1. x 和 y 都至少在生命周期 'a 内有效
// 2. 返回的引用也只在 'a 内有效
// 3. 调用者保证 x 和 y 都足够"长寿"
if x.len() > y.len() {
x // 返回 x,要求 x 在 'a 内有效
} else {
y // 返回 y,要求 y 在 'a 内有效
}
}
fn main() {
let s1 = String::from("long string"); // 's1
let s2 = String::from("short"); // 's2
// 'a 是 's1 和 's2 的交集
let result = longest(&s1, &s2);
// result 的生命周期受限于 s1 和 s2 中较短的那个
println!("结果:{}", result);
} // result 先失效,然后 s2,最后 s1不同生命周期标注的影响
rust
▶ Run// 情况 1:返回与第一个参数相同生命周期
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 只返回 x,与 y 无关
}
// 情况 2:返回与第二个参数相同生命周期
fn second<'a, 'b>(x: &'a str, y: &'b str) -> &'b str {
y // 只返回 y,与 x 无关
}
// 情况 3:返回受限于两者(最常见)
fn either<'a>(x: &'a str, y: &'a str) -> &'a str {
// 可能返回 x 或 y,所以返回受限于两者
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let r1 = first(&s1, &s2); // r1 的生命周期 = s1 的生命周期
let r2 = second(&s1, &s2); // r2 的生命周期 = s2 的生命周期
let r3 = either(&s1, &s2); // r3 受限于 s1 和 s2
}
---
## 小结
- 生命周期标注 `'a` 告诉编译器引用之间的关联关系
- 单一生命周期 `'a` 表示所有引用至少活到 `'a` 结束
- 不同生命周期 `'a`, `'b` 可以表达更精细的约束
- 返回值生命周期受限于输入参数中较短的那个
- 生命周期标注不改变实际生命周期,只是描述约束
## 练习题
详见:[练习题](../../exercises/17-lifetimes)