Skip to content

Latest commit

 

History

History
501 lines (397 loc) · 10.7 KB

File metadata and controls

501 lines (397 loc) · 10.7 KB

第9章:生命周期与标注

生命周期是 Rust 中最独特的特性之一,它让编译器能够在编译时验证引用的有效性。

9.1 目的与核心思想

为什么需要生命周期?

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

fn main() {
    let r;                // r 的生命周期开始
    
    {
        let x = 5;        // x 的生命周期开始
        r = &x;           // 错误!x 的生命周期太短
    }                     // x 的生命周期结束
    
    // println!("r: {}", r); // r 是悬垂引用
}

借用检查器

Rust 编译器有一个借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的:

fn main() {
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
}                         // ----------+

在这个例子中,x 的生命周期 'b 大于 r 的生命周期 'a,所以代码是有效的。

函数中的生命周期

// 这个函数签名没有说明返回的引用与哪个参数相关
// 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
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";
    
    let result = longest(string1.as_str(), string2);
    println!("最长的字符串是 {}", result);
}

9.2 变量生命周期

生命周期的基本概念

每个引用都有一个生命周期,即引用保持有效的作用域:

fn main() {
    let string1 = String::from("long string is long");
    
    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("最长的字符串是 {}", result);
    } // string2 在这里离开作用域
    
    // result 在这里不再有效,因为它可能引用了 string2
}

生命周期与所有权

fn main() {
    let s1 = String::from("hello");
    let s2;
    
    {
        let s3 = String::from("world");
        s2 = longest(&s1, &s3);
        // s2 的生命周期受限于 s3
    } // s3 离开作用域
    
    // println!("{}", s2); // 错误!s2 可能引用已释放的内存
}

静态生命周期

'static 生命周期是特殊的生命周期,它表示引用在整个程序期间都有效:

fn main() {
    // 字符串字面量有 'static 生命周期
    let s: &'static str = "I have a static lifetime.";
    
    // 也可以显式创建静态生命周期的数据
    static HELLO: &str = "Hello, world!";
    
    let r: &'static str = HELLO;
    println!("{}", r);
}

9.3 生命周期标注及规则

生命周期标注语法

生命周期标注使用撇号 ' 开头:

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

函数签名中的生命周期

// 单个生命周期参数
fn foo<'a>(x: &'a str) -> &'a str {
    x
}

// 多个生命周期参数
fn foo2<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x
}

// 可变引用的生命周期
fn foo3<'a>(x: &'a mut i32) {
    *x += 1;
}

生命周期省略规则

Rust 有三条生命周期省略规则,使得在常见情况下不需要显式标注:

  1. 第一条规则:每个引用参数都有自己的生命周期参数
  2. 第二条规则:如果只有一个输入生命周期参数,它被赋予所有输出生命周期参数
  3. 第三条规则:如果方法有多个输入生命周期参数,但其中一个是 &self&mut self,那么 self 的生命周期被赋给所有输出生命周期参数
// 应用规则前
fn first_word(s: &str) -> &str {
    // ...
}

// 应用第一条规则
fn first_word<'a>(s: &'a str) -> &str {
    // ...
}

// 应用第二条规则
fn first_word<'a>(s: &'a str) -> &'a str {
    // ...
}

结构体中的生命周期

// 存储引用的结构体需要生命周期标注
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!("请注意: {}", 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,
    };
}

生命周期约束

// 'a 必须比 'b 活得长
fn foo<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
    x
}

// 在结构体中使用生命周期约束
struct Ref<'a, T: 'a> {
    field: &'a T,
}

// 多个约束
fn bar<'a, 'b, 'c>(x: &'a str, y: &'b str) -> &'c str 
where
    'a: 'c,
    'b: 'c,
{
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

9.4 生命周期示例代码解析

复杂的生命周期场景

// 返回值的生命周期与输入参数相关
fn longest_with_an_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("公告: {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 使用示例
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest_with_an_announcement(
            string1.as_str(),
            string2.as_str(),
            "今天是个好日子",
        );
        println!("最长的字符串是: {}", result);
    }
    // println!("{}", result); // 错误!result 的生命周期受限于 string2
}

方法定义中的生命周期

struct Context<'a>(&'a str);

struct Parser<'a> {
    context: &'a Context<'a>,
}

impl<'a> Parser<'a> {
    fn parse(&self) -> Result<(), &'a str> {
        Err(&self.context.0[1..])
    }
}

fn parse_context(context: Context) -> Result<(), &str> {
    Parser { context: &context }.parse()
}

// 更复杂的例子
struct StrSplit<'a, 'b> {
    remainder: Option<&'a str>,
    delimiter: &'b str,
}

impl<'a, 'b> Iterator for StrSplit<'a, 'b> {
    type Item = &'a str;
    
    fn next(&mut self) -> Option<Self::Item> {
        let remainder = self.remainder.as_mut()?;
        if let Some(next_delim) = remainder.find(self.delimiter) {
            let until_delimiter = &remainder[..next_delim];
            *remainder = &remainder[(next_delim + self.delimiter.len())..];
            Some(until_delimiter)
        } else {
            self.remainder.take()
        }
    }
}

fn str_split<'a, 'b>(haystack: &'a str, delimiter: &'b str) -> StrSplit<'a, 'b> {
    StrSplit {
        remainder: Some(haystack),
        delimiter,
    }
}

fn main() {
    let haystack = "a b c d e";
    let letters: Vec<_> = str_split(haystack, " ").collect();
    println!("{:?}", letters);
}

生命周期与闭包

fn make_a_cloner<'a>(s: &'a str) -> impl Fn() -> &'a str {
    move || s
}

fn main() {
    let s = String::from("hello");
    let cloner = make_a_cloner(&s);
    
    let s_ref = cloner();
    println!("{}", s_ref);
    
    drop(s); // s 被释放后,cloner 不能再使用
    // let s_ref2 = cloner(); // 错误!
}

高阶生命周期

// Higher-Ranked Trait Bounds (HRTB)
fn call_with_ref<F>(f: F) 
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let s = String::from("hello");
    let result = f(&s);
    println!("{}", result);
}

fn main() {
    call_with_ref(|s| s);
    call_with_ref(|s| &s[1..]);
}

生命周期与类型推断

// 编译器可以推断生命周期
fn foo(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 但在某些情况下需要显式标注
struct Wrapper<'a> {
    value: &'a str,
}

impl<'a> Wrapper<'a> {
    fn new(value: &'a str) -> Self {
        Wrapper { value }
    }
    
    // 这里需要显式的生命周期
    fn combine<'b>(&'a self, other: &'b str) -> &'a str 
    where 
        'b: 'a 
    {
        if self.value.len() > other.len() {
            self.value
        } else {
            other
        }
    }
}

实际应用示例

use std::fmt::Display;

// 一个缓存系统的例子
struct Cache<'a, T> {
    data: Vec<&'a T>,
}

impl<'a, T: Display> Cache<'a, T> {
    fn new() -> Self {
        Cache { data: Vec::new() }
    }
    
    fn insert(&mut self, value: &'a T) {
        self.data.push(value);
    }
    
    fn get(&self, index: usize) -> Option<&&'a T> {
        self.data.get(index)
    }
    
    fn display_all(&self) {
        for (i, item) in self.data.iter().enumerate() {
            println!("Cache[{}]: {}", i, item);
        }
    }
}

fn main() {
    let value1 = String::from("Hello");
    let value2 = String::from("World");
    
    let mut cache = Cache::new();
    cache.insert(&value1);
    cache.insert(&value2);
    
    cache.display_all();
    
    // value1 和 value2 必须比 cache 活得长
    drop(cache);
    println!("Values still exist: {} {}", value1, value2);
}

生命周期的最佳实践

// 1. 尽可能使用生命周期省略
fn process(input: &str) -> &str {
    &input[1..]
}

// 2. 必要时才添加生命周期标注
fn process_two<'a>(first: &'a str, second: &str) -> &'a str {
    // 只返回 first,所以只需要 'a
    first
}

// 3. 避免不必要的生命周期约束
// 不好的做法
fn bad<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
    x
}

// 好的做法
fn good<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

// 4. 使用结构体时明确生命周期关系
struct Document<'a> {
    content: &'a str,
}

struct DocumentProcessor<'a> {
    current_doc: Option<Document<'a>>,
}

impl<'a> DocumentProcessor<'a> {
    fn process(&mut self, doc: Document<'a>) {
        // 处理文档
        self.current_doc = Some(doc);
    }
}

小结

本章深入学习了 Rust 的生命周期系统:

  1. 核心思想:生命周期确保引用始终有效,防止悬垂引用
  2. 生命周期标注:使用 'a 等符号标注引用的生命周期
  3. 省略规则:编译器可以在常见情况下推断生命周期
  4. 实际应用:在复杂场景中正确使用生命周期标注

生命周期是 Rust 保证内存安全的关键机制之一,虽然初学时可能觉得复杂,但随着实践会逐渐理解其价值。下一章我们将学习特征对象,探讨 Rust 中的动态分发机制。