8·所有权系统进阶

生命周期基础

生命周期基础

学习目标

  1. 理解生命周期是什么
  2. 掌握函数签名中的生命周期标注
  3. 理解生命周期省略规则
  4. 掌握结构体中的生命周期

核心概念

为什么需要生命周期?

编译器需要确保引用不会比它指向的数据活得更久:

fn longest(x: &str, y: &str) -> &str {  // ❌ 编译器不知道返回值的生命周期
    if x.len() > y.len() { x } else { y }
}

编译器问:返回的引用指向 x 还是 y?如果 x 先被 drop,返回的引用就悬垂了。

生命周期标注

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    //        ^^ 生命周期参数
    //              ^^ x 和 y 至少活这么久
    //                              ^^ 返回值也至少活这么久
    if x.len() > y.len() { x } else { y }
}

含义:返回的引用的生命周期 = x 和 y 中较短的那个。

fn main() {
    let string1 = String::from("long string");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
        println!("更长的是: {}", result);  // ✅ string2 还活着
    }
    // println!("{}", result);  // ❌ string2 已被 drop
}

不需要生命周期的情况

// 返回值只跟第一个参数有关 → 只需要标注一个
fn first_word(s: &str) -> &str {
    // 编译器自动推断(生命周期省略规则)
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

生命周期省略规则

编译器按三条规则自动推断,不需要手动标注:

规则 1:每个引用参数都有自己的生命周期
   fn f(x: &str, y: &str) → fn f<'a, 'b>(x: &'a str, y: &'b str)

规则 2:只有一个输入生命周期,它赋给所有输出
   fn f(x: &str) -> &str → fn f<'a>(x: &'a str) -> &'a str

规则 3:方法中 &self 的生命周期赋给所有输出
   fn f(&self, x: &str) -> &str → fn f<'a>(&'a self, x: &str) -> &'a str

如果三条规则都不能确定返回值的生命周期,编译器报错,需要手动标注。

结构体中的生命周期

结构体持有引用时,必须标注生命周期:

struct Excerpt<'a> {
    text: &'a str,  // 这个引用至少活到 Excerpt 实例被 drop
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence;
    {
        let i = novel.find('.').unwrap_or(novel.len());
        first_sentence = Excerpt {
            text: &novel[..i],
        };
    }
    println!("{}", first_sentence.text);
}

'static 生命周期

// 'static 表示整个程序运行期间都有效
let s: &'static str = "我存在整个程序生命周期";

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

实践练习

练习 1:修复生命周期错误

// ❌ 编译错误,修复它
fn first_or_second(first: &str, second: &str, use_first: bool) -> &str {
    if use_first { first } else { second }
}

// ✅ 修复
fn first_or_second<'a>(first: &'a str, second: &'a str, use_first: bool) -> &'a str {
    if use_first { first } else { second }
}

练习 2:结构体生命周期

struct Highlight<'a> {
    text: &'a str,
    color: &'a str,
}

impl<'a> Highlight<'a> {
    fn render(&self) -> String {
        format!("[{}]{}", self.color, self.text)
    }
}

fn main() {
    let text = String::from("重要信息");
    let h = Highlight {
        text: &text,
        color: "red",
    };
    println!("{}", h.render());
}

练习 3:理解省略规则

// 以下函数都不需要手动标注生命周期,为什么?

fn get_first(s: &str) -> &str {        // 规则 2 适用
    s.split_whitespace().next().unwrap_or("")
}

struct Parser {
    input: String,
}

impl Parser {
    fn remaining(&self) -> &str {       // 规则 3 适用
        &self.input
    }
}

常见错误

1. 返回局部变量的引用

// ❌ s 在函数结束时被 drop
fn create() -> &str {
    let s = String::from("hello");
    &s
}

// ✅ 返回所有权
fn create() -> String {
    String::from("hello")
}

2. 生命周期不匹配

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x  // ✅ 可以返回 x
    // y  // ❌ 不能返回 y,因为 y 的生命周期和 'a 不关联
}

3. 过度标注

// ⚠️ 不需要标注(编译器能推断)
fn first<'a>(s: &'a str) -> &'a str {
    &s[..1]
}

// ✅ 省略
fn first(s: &str) -> &str {
    &s[..1]
}

小结

概念说明
'a生命周期参数
&'a T生命周期为 'a 的引用
'static整个程序运行期间有效
省略规则编译器自动推断,大多数情况不需要手动标注

生命周期的本质:告诉编译器多个引用之间的存活关系,确保不会悬垂。

练习编辑器

rust
Loading...