Skip to content

Rust 生命周期

生命周期是 Rust 中用于确保引用有效性的机制。本章节将介绍 Rust 中的生命周期概念。

生命周期的概念

生命周期是指引用有效的时间段。在 Rust 中,每个引用都有一个生命周期,编译器会检查引用的生命周期是否有效。

为什么需要生命周期

生命周期的主要目的是防止悬垂引用(dangling references):

rust
fn main() {
    let r;
    {
        let x = 5;
        r = &x; // 错误:x 的生命周期短于 r
    }
    // println!("r = {}", r); // x 已经离开作用域
}

生命周期注解

生命周期注解使用撇号 ' 表示:

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

这里的 'a 是一个生命周期参数,它表示 xy 和返回值的生命周期是相同的。

生命周期省略规则

Rust 编译器有一套生命周期省略规则,可以在某些情况下省略生命周期注解:

  1. 每个参数的生命周期都有自己的生命周期参数:对于 fn foo(x: &T),会自动添加 fn foo<'a>(x: &'a T)

  2. 如果只有一个输入生命周期参数,那么它会被赋给所有输出生命周期参数:对于 fn foo(x: &T) -> &U,会自动添加 fn foo<'a>(x: &'a T) -> &'a U

  3. 如果有多个输入生命周期参数,但其中一个是 &self&mut self,那么 self 的生命周期会被赋给所有输出生命周期参数:对于 impl T { fn foo(&self) -> &U },会自动添加 impl T { fn foo<'a>(&'a self) -> &'a U }

结构体中的生命周期

当结构体包含引用时,需要为结构体添加生命周期注解:

rust
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    println!("Important excerpt: {}", i.part);
}

方法中的生命周期

为结构体实现方法时,也需要考虑生命周期:

rust
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
    
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    println!("Level: {}", i.level());
    println!("Announcement: {}", i.announce_and_return_part("Hello!"));
}

静态生命周期

'static 是一个特殊的生命周期,表示引用可以在整个程序运行期间有效:

rust
fn main() {
    let s: &'static str = "Hello, world!";
    println!("s = {}", s);
}

字符串字面量默认具有 'static 生命周期。

生命周期的实际应用

解决悬垂引用

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s1 = String::from("hello");
    let result;
    {
        let s2 = String::from("world");
        result = longest(&s1, &s2); // 错误:s2 的生命周期短于 result
    }
    // println!("Longest: {}", result);
}

正确的使用方式

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world");
    let result = longest(&s1, &s2);
    println!("Longest: {}", result);
}

总结

  • 生命周期:引用有效的时间段
  • 生命周期注解:使用 'a 等符号表示
  • 生命周期省略规则:编译器自动推断生命周期的规则
  • 结构体中的生命周期:当结构体包含引用时需要添加生命周期注解
  • 静态生命周期'static,表示引用可以在整个程序运行期间有效

生命周期是 Rust 类型系统的重要组成部分,它确保了引用的有效性,防止了悬垂引用等内存安全问题。通过本章节的学习,你应该已经理解了 Rust 中的生命周期概念和使用方法。