生命周期初步概念

116 阅读3分钟

生命周期初步概念

为什么需要生命周期?

假设有以下代码:

fn main() {
    let r;
    {
        let x = 5;       // x的作用域在内部块
        r = &x;          // r试图借用x
    } // x离开作用域,被释放
    println!("r = {}", r); // 错误!r成为悬垂引用
}
  • 编译错误:borrowed value does not live long enough
  • 根本问题:r(引用)的生命周期不能超过它指向的数据(x)的生命周期

生命周期标注语法

  1. 基本形式
    生命周期参数以 ' 开头,通常用短名称如 'a

    &i32        // 普通引用
    &'a i32     // 带有显式生命周期的引用
    &'a mut i32 // 可变引用带生命周期
    
  2. 结构体中的生命周期
    如果结构体包含引用,必须标注生命周期:

    struct Book<'a> { // 表示结构体的生命周期不超过字段`title`的生命周期
        title: &'a str,
    }
    
    fn main() {
        let title = String::from("Rust Book");
        let book = Book { title: &title }; // title的生命周期必须长于book
        // 如果title被提前释放,book.title会无效
    }
    

函数中的生命周期标注

示例:返回两个字符串切片中较长的

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() { s1 } else { s2 }
}

fn main() {
    let s1 = String::from("short");
    let result;
    {
        let s2 = String::from("very long string");
        result = longest(s1.as_str(), s2.as_str()); 
        // 此时result的生命周期取决于s1和s2中更短的那个
        println!("最长的字符串是: {}", result); // 正确:此时s2仍有效
    }
    // println!("{}", result); // 错误!s2已释放,result成为悬垂引用
}

生命周期标注的含义

  • longest<'a>:声明一个生命周期参数 'a
  • s1: &'a str, s2: &'a str:两个参数必须具有相同生命周期 'a
  • -> &'a str:返回值的生命周期与参数相同
  • 核心规则:返回值的生命周期不能超过输入参数的生命周期

生命周期省略规则

在简单场景下,编译器可以自动推断生命周期,无需手动标注。例如:

// 以下情况可省略生命周期标注
fn first_word(s: &str) -> &str { // 实际等价于 first_word<'a>(s: &'a str) -> &'a str
    // ...
}

编译器根据三个规则自动推断:

  1. 每个输入引用自动获得一个生命周期参数
  2. 如果只有一个输入生命周期,它被赋予所有输出生命周期
  3. 如果是方法(&self&mut self),输出生命周期与self的生命周期一致

练习题

  1. 修复结构体错误
    以下代码为何报错?如何修复?

    struct Excerpt<'a> {
        part: &'a str,
    }
    
    fn main() {
        let novel = String::from("Call me Ishmael. Some years ago...");
        let first_sentence = novel.split('.').next().unwrap();
        let excerpt = Excerpt { part: first_sentence };
        // 必须确保novel的生命周期长于excerpt
    }
    
  2. 实现一个返回自身字段的函数
    Book结构体添加方法get_title,返回title的引用:

    impl<'a> Book<'a> {
        fn get_title(&self) -> &str {
            self.title
        }
    }
    
  3. 挑战:跨作用域的生命周期
    编写函数create_excerpt,从字符串切片生成Excerpt结构体:

    fn create_excerpt<'a>(text: &'a str, delimiter: char) -> Excerpt<'a> {
        let part = text.split(delimiter).next().unwrap();
        Excerpt { part }
    }
    

常见错误分析

错误示例:

fn get_str() -> &str { // 缺少生命周期标注
    let s = String::from("hello");
    &s // 返回局部变量的引用(危险!)
}
  • 错误信息:missing lifetime specifier
  • 修复:不能返回局部变量的引用!必须确保返回的引用指向有效数据。