结构体中的生命周期
> 掌握结构体生命周期标注的方法,学会处理多字段和多生命周期的场景。
结构体中的生命周期详解
为什么结构体需要生命周期标注
rust
▶ Run// ❌ 错误:引用字段必须标注生命周期
struct Excerpt {
part: &str, // 缺少生命周期标注
}
// 编译错误:
// error[E0106]: missing lifetime specifier
// --> src/main.rs:2:11
// |
// 2 | part: &str,
// | ^ expected named lifetime parameter
// |
// = help: consider introducing a named lifetime parameter
// help: consider introducing a named lifetime parameter
// |
// 2 | part: &'a str,
// | ++++
// ✅ 正确
struct Excerpt<'a> {
part: &'a str,
}
// 含义:Excerpt 实例的生命周期受限于 'a
// 即 part 引用必须在 Excerpt 存在期间有效内存布局分析:
结构体包含引用的内存布局:
struct Excerpt<'a> {
part: &'a str,
}
栈内存:
┌────────────────────┐
│ Excerpt │
│ ┌────────────────┐ │
│ │ part (指针) │ │
│ │ ptr ─────┐ │ │
│ │ len │ │ │
│ └────────────┼───┘ │
└──────────────┼─────┘
│
↓
┌────────────────────┐
│ 原始数据 │
│ "Hello, World!" │
│ (String 或 &'str) │
└────────────────────┘
约束:原始数据的生命周期必须 ≥ Excerpt 的生命周期
时间线示例:
let document = String::from("Rust is great"); // 'document 开始
let excerpt = Excerpt { part: &document }; // 'excerpt 开始
┌────────────────────────────────────────────┐
│ document: "Rust is great" │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ excerpt │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ ↑ │
│ excerpt.part 指向 document │
└────────────────────────────────────────────┘
✅ excerpt 的生命周期 ≤ document 的生命周期
反例(错误):
let excerpt: Excerpt;
{
let document = String::from("Rust is great");
excerpt = Excerpt { part: &document };
} // document 在这里被释放
println!("{}", excerpt.part); // ❌ 访问已释放的内存
时间线:
┌────────────────────────────────────────────┐
│ excerpt (外部作用域) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ { document (内部作用域) │
│ ━━━━━━━━━━━━━━━━━ } ← document 失效 │
│ ↑ │
│ excerpt.part 仍指向已释放的 document │
└────────────────────────────────────────────┘
❌ excerpt 的生命周期 > document 的生命周期单字段生命周期
rust
▶ Run#[derive(Debug)]
struct Reference<'a> {
data: &'a i32,
}
fn main() {
let value = 42; // 'value 开始
let reference = Reference {
data: &value, // reference 的生命周期 ⊆ 'value
};
println!("引用值:{}", reference.data);
} // reference 先失效,然后 value 失效多字段生命周期
rust
▶ Run// 相同生命周期:两个字段受限于同一个生命周期
struct SameLifetime<'a> {
field1: &'a str,
field2: &'a str,
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let same = SameLifetime {
field1: &s1, // 都必须有生命周期 ≥ 'a
field2: &s2, // s1 和 s2 必须至少活到 'a 结束
};
// s1 和 s2 必须在同一作用域内有效
}
// 不同生命周期:两个字段有独立的约束
struct DifferentLifetime<'a, 'b> {
short: &'a str, // 短生命周期字段
long: &'b str, // 长生命周期字段
}
fn main() {
let long_string = String::from("long string"); // 'long 开始
let short_string = String::from("short"); // 'short 开始
let diff = DifferentLifetime {
short: &short_string, // 'short
long: &long_string, // 'long
};
// diff 的生命周期受限于 'short(较短者)
// ✅ 可以只使用 long_string
{
let only_long = DifferentLifetime {
short: &short_string,
long: &long_string,
};
} // only_long 失效,但 long_string 仍然有效
println!("{}", diff.long); // ✅ 仍然有效
}
// 时间线:
┌────────────────────────────────────────────┐
│ long_string │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ short_string │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ ↑ │
│ diff.short 指向这里 │
│ │
│ diff.long 指向 long_string │
└────────────────────────────────────────────┘带方法的结构体
rust
▶ Run#[derive(Debug)]
struct Text<'a> {
content: &'a str,
source: &'a str,
}
impl<'a> Text<'a> {
// 构造函数
fn new(content: &'a str, source: &'a str) -> Self {
Text { content, source }
}
// 返回引用的方法(省略规则 3)
fn get_content(&self) -> &str {
// 等价于:fn get_content<'b>(&'b self) -> &'b str
// 但 self.content 的生命周期是 'a,不是 'b
// 实际上返回 &'a str(生命周期协变)
self.content
}
fn get_source(&self) -> &str {
self.source
}
// 返回与 self 相同生命周期的引用
fn get_metadata(&self) -> (&str, &str) {
(self.content, self.source)
}
// 方法可以返回不同的生命周期
fn with_prefix(&self, prefix: &str) -> String {
format!("{}{}", prefix, self.content)
}
}
fn main() {
let source = String::from("Original text");
let text = Text::new(&source, &source);
println!("Content: {}", text.get_content());
println!("Source: {}", text.get_source());
}方法生命周期的内存布局:
impl<'a> Text<'a> {
fn get_content(&self) -> &str {
self.content
}
}
时间线:
┌────────────────────────────────────────────┐
│ source: "Original text" │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ 'source
│ │
│ text │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ 'text ⊆ 'source
│ │
│ let content = text.get_content() │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ 'content ⊆ 'source
│ ↑ │
│ content 指向 text.content │
│ 而 text.content 指向 source │
└────────────────────────────────────────────┘
生命周期链:
content (引用) ──> text.content (字段引用) ──> source (String)
'content 'a 'source
约束:'source ≥ 'a ≥ 'content小结
- 结构体包含引用字段必须标注生命周期
struct Name<'a> { field: &'a T }定义带生命周期的结构体- 多字段可以共享同一生命周期或使用不同生命周期
- 结构体方法遵循省略规则,返回引用时与 self 关联
- 结构体实例的生命周期不能超过其引用字段的生命周期
练习题
详见:练习题