Skip to content

结构体中的生命周期

> 掌握结构体生命周期标注的方法,学会处理多字段和多生命周期的场景。

结构体中的生命周期详解

为什么结构体需要生命周期标注

rust
// ❌ 错误:引用字段必须标注生命周期
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 存在期间有效
▶ Run

内存布局分析:

结构体包含引用的内存布局:

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
#[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 失效
▶ Run

多字段生命周期

rust
// 相同生命周期:两个字段受限于同一个生命周期
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            │
└────────────────────────────────────────────┘
▶ Run

带方法的结构体

rust
#[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());
}
▶ Run

方法生命周期的内存布局:

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 关联
  • 结构体实例的生命周期不能超过其引用字段的生命周期

练习题

详见:练习题