Rust 生命周期

1. 什么是生命周期?

生命周期是引用有效的作用域范围。在 Rust 中,每个引用都有一个生命周期,这是它保持有效的作用域。

fn main() {
    // 生命周期开始
    let x = 5;            // x 的生命周期开始
    let r = &x;           // r 的生命周期开始,引用 x
    
    println!("r = {}", r);
    
    // 生命周期结束
} // x 和 r 的生命周期结束

2. 为什么需要生命周期?

问题:悬垂引用(Dangling References)

// 这个代码无法编译!
fn main() {
    let reference;
    {
        let value = 42;        // value 在内部作用域创建
        reference = &value;    // reference 引用 value
    } // value 在这里被丢弃,内存被释放
    
    // 错误!reference 现在指向已释放的内存
    println!("reference: {}", reference);
}

编译器错误

error[E0597]: `value` does not live long enough

生命周期的目的

  • 防止悬垂引用
  • 确保内存安全
  • 在编译时进行检查

3. 生命周期注解语法

基本语法

&i32        // 普通引用
&'a i32     // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

生命周期参数命名

  • ' 开头
  • 通常使用短名称:'a, 'b, 'c
  • 'static 是特殊生命周期

4. 函数中的生命周期

问题场景

// 无法编译:不知道返回的引用与哪个参数相关
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

解决方案:添加生命周期参数

// 正确版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

详细解释 <'a>

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
  • <'a>:声明一个生命周期参数 'a
  • x: &'a str:参数 x 是一个字符串切片,具有生命周期 'a
  • y: &'a str:参数 y 也是一个字符串切片,具有相同的生命周期 'a
  • -> &'a str:返回值也具有生命周期 'a

实际含义:返回的引用将和两个输入参数中生命周期较短的那个一样长。

使用示例

fn main() {
    let string1 = String::from("很长的字符串");
    
    {
        let string2 = String::from("短字符串");
        let result = longest(string1.as_str(), string2.as_str());
        println!("最长的字符串是: {}", result);
        // result 的生命周期与 string2 相同
    } // string2 和 result 在这里离开作用域
    
    // 这里不能再使用 result
}

5. 多个生命周期参数

// 两个参数有不同的生命周期
fn multiple_lifetimes<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    println!("y = {}", y);  // 只是使用 y,但不返回它
    x  // 只返回 x,所以返回类型是 &'a str
}

fn main() {
    let string1 = String::from("字符串1");
    let result;
    
    {
        let string2 = String::from("字符串2");
        result = multiple_lifetimes(string1.as_str(), string2.as_str());
    } // string2 离开作用域,但 result 仍然有效,因为它只依赖 string1
    
    println!("result = {}", result); // 可以正常使用
}

6. 结构体中的生命周期

包含引用的结构体

// 结构体包含引用,需要生命周期注解
struct Book<'a> {
    title: &'a str,
    author: &'a str,
}

fn main() {
    let title = String::from("Rust 编程");
    let author = String::from("张三");
    
    let book = Book {
        title: &title,
        author: &author,
    };
    
    println!("《{}》 by {}", book.title, book.author);
}

重要规则

结构体实例不能比它包含的引用活得更久

fn create_book() -> Book<'static> {
    let title = String::from("临时标题");  // title 在函数结束时被丢弃
    let author = String::from("临时作者"); // author 在函数结束时被丢弃
    
    // 错误!返回的 Book 包含对局部变量的引用
    Book {
        title: &title,    // title 将在函数结束时被释放
        author: &author,  // author 将在函数结束时被释放
    }
} // title 和 author 在这里被丢弃

7. 方法实现中的生命周期

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

// 为带有生命周期的结构体实现方法
impl<'a> ImportantExcerpt<'a> {
    // 规则3适用:返回值的生命周期与 &self 相同
    fn get_part(&self) -> &str {
        self.part
    }
    
    // 多个参数的情况
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("请注意: {}", announcement);
        self.part
    }
    
    // 静态方法,不依赖 self
    fn new(part: &'a str) -> ImportantExcerpt<'a> {
        ImportantExcerpt { part }
    }
}

8. 生命周期省略规则

Rust 编译器有3条自动推断规则,避免不必要的生命周期注解:

规则1:每个引用参数获得自己的生命周期

fn first_word(s: &str) -> &str
// 编译器推断为:
fn first_word<'a>(s: &'a str) -> &'a str

规则2:如果只有一个输入生命周期,它被赋予所有输出生命周期

fn first_word(s: &str) -> &str
// 应用规则2后:
fn first_word<'a>(s: &'a str) -> &'a str

规则3:如果有多个输入生命周期,但其中一个是 &self 或 &mut self,那么 self 的生命周期被赋予所有输出生命周期

impl SomeStruct {
    fn get_value(&self, key: &str) -> &str
    // 应用规则3后:
    fn get_value<'a, 'b>(&'a self, key: &'b str) -> &'a str
}

9. 手动生命周期注解的场景

需要显式注解的情况:

  1. 函数返回引用,且输入参数有多个引用
  2. 结构体包含引用
  3. trait 对象包含引用
  4. 闭包涉及引用
// 必须手动注解的例子
fn problematic_function<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    // 复杂的逻辑...
    x
}

10. 静态生命周期 'static

'static 生命周期表示整个程序的持续时间。

// 字符串字面量有 'static 生命周期
let s: &'static str = "我是字符串字面量";

// 函数返回 'static 生命周期
fn get_static_str() -> &'static str {
    "我会永远存在"
}

// 在结构体中使用
struct GlobalData {
    name: &'static str,
    version: &'static str,
}

fn main() {
    let data = GlobalData {
        name: "MyApp",
        version: "1.0.0",
    };
}

11. 实际示例分析

示例1:解析字符串

fn parse_first_number<'a>(s: &'a str) -> Option<&'a str> {
    for (i, c) in s.char_indices() {
        if c.is_digit(10) {
            let start = i;
            let end = s[i..].find(|c: char| !c.is_digit(10))
                           .map(|pos| i + pos)
                           .unwrap_or(s.len());
            return Some(&s[start..end]);
        }
    }
    None
}

fn main() {
    let text = String::from("这里有123个苹果和456个香蕉");
    
    if let Some(number) = parse_first_number(&text) {
        println!("找到数字: {}", number);
        // number 的生命周期与 text 相同
    }
}

示例2:缓存系统

struct Cache<'a, T> {
    data: &'a [T],
    timestamp: u64,
}

impl<'a, T> Cache<'a, T> {
    fn new(data: &'a [T]) -> Self {
        Cache {
            data,
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        }
    }
    
    fn get_data(&self) -> &'a [T] {
        self.data
    }
}

12. 常见错误和解决方案

错误1:返回局部变量的引用

// 错误!
fn bad_function() -> &str {
    let s = String::from("hello");
    &s  // s 将在函数结束时被丢弃
}

// 正确:返回 String 而不是引用
fn good_function() -> String {
    let s = String::from("hello");
    s
}

错误2:生命周期不匹配

// 错误!
fn process_data<'a>(data: &'a str, default: &str) -> &'a str {
    if data.is_empty() {
        default  // 错误:default 可能比 'a 生命周期短
    } else {
        data
    }
}

// 正确:确保两个参数有相同的生命周期约束
fn process_data<'a>(data: &'a str, default: &'a str) -> &'a str {
    if data.is_empty() {
        default
    } else {
        data
    }
}

Read more

Rust 结构体、枚举、模式匹配、Option 和 Result、包和模块

1. 结构体(Structs)深度解析 什么是结构体? 结构体是一种自定义数据类型,允许你将多个相关的值组合在一起,形成一个有意义的组合。 定义和使用结构体 // 1. 基本结构体定义 struct User { username: String, // 字段:用户名 email: String, // 字段:邮箱 sign_in_count: u64, // 字段:登录次数 active: bool, // 字段:是否活跃 } // 2. 创建结构体实例 fn create_user() { // 不可变实例 let user1 = User { email: String::from("alice@example.com"), username: String:

By amm

rust所有权、借用、生命周期

1. 所有权 所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收即可保证内存安全。 所有权规则 1. Rust 中的每一个值都有一个被称为其所有者的变量。 2. 值在任一时刻有且只有一个所有者。 3. 当所有者(变量)离开作用域,这个值将被丢弃。 变量与数据交互的方式 移动 fn main() { let s1 = String::from("hello"); // s1 拥有字符串 "hello" let s2 = s1; // s1 的所有权被移动(move)到 s2 // println!("{}, world!", s1); // 这行会编译错误!s1

By amm

© 2025 路不易All rights reserved.

黔ICP备2025043243号-1 | 公安备案图标 贵公网安备52052402000220号