Skip to content

第五节、生命周期

生命周期

在“引用和借用”部分没有讨论的一个细节是,Rust 中的每个引用都有一个生命周期,这是该引用有效的范围。大多数时候,生命周期是隐含的和推断的,就像大多数时候,类型是推断的。当可能有多种类型时,我们必须注释类型。以类似的方式,当引用的生命周期可以以几种不同的方式相关时,我们必须注释生命周期。Rust 要求我们使用通用生命周期参数来注释关系,以确保在运行时使用的实际引用肯定是有效的。

其他语言有作用域问题,和这个概念类似

    {
        let r;

        {
            let x = 5;
            r = &x;
        }

        println!("r: {}", r);
    }
这段代码就存在生命周期问题,x的生命周期范围见下图:
    {
        let r;                // ---------+-- 'a
                              //          |
        {                     //          |
            let x = 5;        // -+-- 'b  |
            r = &x;           //  |       |
        }                     // -+       |
                              //          |
        println!("r: {}", r); //          |
    }                         // ---------+
x 有效范围在 b,而println里面的r在a,所以这里打印使用r的时候,他是没有有效值的。

如果此时修改一个打印语句的位置,代码就可以编译通过

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

函数引用参数生命周期

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}
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
    }
}

结构体中定义的生命周期

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

省略的生命周期

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
这个函数在没有生命周期注释的情况下编译的原因是历史性的:在 Rust 的早期版本(1.0 之前)中,这段代码不会编译,因为每个引用都需要一个显式的生命周期。那时,函数签名应该是这样写的:

fn first_word<'a>(s: &'a str) -> &'a str {
在编写了大量 Rust 代码后,Rust 团队发现 Rust 程序员在特定情况下一遍又一遍地输入相同的生命周期注释。这些情况是可以预测的,并遵循一些确定的模式。开发人员将这些模式编程到编译器的代码中,因此借用检查器可以推断这些情况下的生命周期,并且不需要显式注释。

静态寿命 我们需要讨论的一个特殊生命周期是'static,这意味着这个引用可以在整个程序期间都存在。所有字符串文字都有'static生命周期,我们可以如下注释:

let s: &'static str = "I have a static lifetime.";
该字符串的文本直接存储在程序的二进制文件中,该二进制文件始终可用。因此,所有字符串文字的生命周期都是 'static.

'static您可能会在错误消息中看到使用生命周期的建议。但在指定'static引用的生命周期之前,请考虑您拥有的引用是否实际上存在于程序的整个生命周期中。您可能会考虑是否希望它活得那么久,即使它可以。大多数情况下,问题是由于尝试创建悬空引用或可用生命周期不匹配造成的。在这种情况下,解决方案是解决这些问题,而不是指定'static生命周期。