用户工具

站点工具


rust:第十四章生命周期

第十四章 生命周期

14.1 为什么需要生命周期

Rust需要在编译时确保所有引用都是有效的。生命周期就是用来追踪引用的有效范围的。

核心问题: 编译器如何知道引用是否指向有效的数据?

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

这段代码编译失败,因为r的生命周期'a比x的生命周期'b长。

14.2 生命周期标注语法

标注规则

  • 以'开头,通常小写且简短,如'a、'b
  • 放在&后面,如&'a i32
  • 只是描述引用之间的关系,不改变实际生命周期

示例

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

14.3 函数中的生命周期

生命周期省略规则

Rust有生命周期省略规则,在简单情况下自动推断:

1. 每个引用参数都有自己的生命周期
2. 如果只有一个输入生命周期,它赋给所有输出生命周期
3. 如果有多个输入生命周期,但一个是&self或&mut self,self的生命周期赋给输出

显式标注示例

// 编译错误:编译器无法推断返回值的生命周期
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
    }
}

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

使用示例

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);
    }
}

错误使用示例

fn main() {
    let string1 = String::from("long string is long");
    let result;
 
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
 
    println!("最长的字符串是{}", result);  // 错误!string2已失效
}

14.4 结构体中的生命周期

含引用的结构体

如果结构体包含引用,必须标注生命周期:

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("找不到.");
 
    let i = ImportantExcerpt {
        part: first_sentence,
    };
 
    println!("{}", i.part);
}

含义: ImportantExcerpt的实例不能比part中的引用活得更长。

14.5 生命周期的省略

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

fn foo<'a>(x: &'a i32)  // 一个参数
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)  // 两个参数

规则2:单个输入生命周期赋给输出

fn foo<'a>(x: &'a i32) -> &'a i32  // 省略写法:fn foo(x: &i32) -> &i32

规则3:&self的生命周期赋给输出

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {  // 省略写法
        3
    }
}

14.6 静态生命周期

'static表示整个程序运行期间都有效。

let s: &'static str = "我有静态生命周期";

注意:

  • 字符串字面量有'static生命周期
  • 不要盲目使用'static来解决生命周期错误
  • 通常意味着设计问题

14.7 泛型类型参数、Trait Bound和生命周期

use std::fmt::Display;
 
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
    }
}

14.8 生命周期实践

返回引用时的生命周期

struct Parser<'a> {
    content: &'a str,
}
 
impl<'a> Parser<'a> {
    fn new(content: &'a str) -> Self {
        Parser { content }
    }
 
    fn first_word(&self) -> &'a str {
        self.content.split_whitespace().next().unwrap_or("")
    }
}

多个生命周期

fn split<'a, 'b>(input: &'a str, delimiter: &'b str) -> Vec<&'a str> {
    input.split(delimiter).collect()
}

练习题

练习题14.1:修复生命周期错误

fn get_first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}
 
fn main() {
    let text = String::from("hello world");
    let word = get_first_word(&text);
    println!("{}", word);
}

答案: 使用生命周期省略规则即可,不需要显式标注。

练习题14.2:实现包含引用的结构体

struct Config<'a> {
    filename: &'a str,
    content: &'a str,
}
 
impl<'a> Config<'a> {
    fn new(filename: &'a str, content: &'a str) -> Self {
        Config { filename, content }
    }
 
    fn display(&self) {
        println!("文件:{}", self.filename);
        println!("内容:{}", self.content);
    }
}
 
fn main() {
    let filename = "config.txt";
    let content = "key=value";
 
    let config = Config::new(filename, content);
    config.display();
}

本章小结

本章学习了Rust的生命周期:

  • 生命周期的目的:确保引用总是有效
  • 标注语法:'a、&'a T
  • 函数生命周期:返回值与输入参数关联
  • 结构体生命周期:含引用的结构体需要标注
  • 省略规则:简化常见情况的标注
  • 'static:整个程序运行期间有效

生命周期是Rust内存安全保证的重要组成部分,虽然起初难以理解,但掌握后能写出更安全的代码。

rust/第十四章生命周期.txt · 最后更改: 127.0.0.1