使用 Lifetimes 验证引用

Lifetimes 是我们已经在使用的另一种通用词。而 比确保类型具有我们想要的行为相比,生命周期确保 只要我们需要,引用就是有效的。

我们在“参考资料和 Borrowing“部分是 Rust 中的每个引用都有一个生命周期,这是 该引用是有效的。大多数时候,生命周期是隐含的和推断的, 就像大多数时候一样,类型是推断的。我们只需要注释类型 当可以使用多种类型时。以类似的方式,我们必须注释生命周期 当引用的生命周期可以以几种不同的方式关联时。锈 要求我们使用通用生命周期参数对关系进行注释,以 确保在运行时使用的实际引用肯定有效。

注释生命周期不是大多数其他编程语言所具有的概念,因此 这会让人感觉很陌生。虽然我们不会涵盖他们的 在本章中,我们将讨论您可能遇到的常见方式 lifetime 语法,以便您熟悉这个概念。

使用 Lifetimes 防止悬空引用

生命周期的主要目的是防止悬空引用,这会导致 程序来引用它要引用的数据以外的数据。 考虑示例 10-16 中的程序,它有一个 outer 作用域和一个 inner 范围。

fn main() {
    let r;

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

    println!("r: {r}");
}

示例 10-16:尝试使用其值 已超出范围

注意:示例 10-16、10-17 和 10-23 中的示例声明了变量 而不给它们一个初始值,所以变量名存在于外部的 范围。乍一看,这似乎与 Rust 的 无 null 值。但是,如果我们尝试在给变量赋值之前使用变量, 我们将得到一个编译时错误,这表明 Rust 确实不允许 null 值。

外部范围声明一个名为r没有初始值,并且 inner scope 声明一个名为x初始值为5.里面 内部作用域中,我们尝试将r作为对x.然后 内部作用域结束,我们尝试在r.此代码不会 compile 的r之前已超出范围 我们尝试使用它。错误消息如下:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:13
  |
5 |         let x = 5;
  |             - binding `x` declared here
6 |         r = &x;
  |             ^^ borrowed value does not live long enough
7 |     }
  |     - `x` dropped here while still borrowed
8 |
9 |     println!("r: {r}");
  |                  --- borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

错误消息指出变量x“活得不够长。”这 原因是x当内部范围在第 7 行结束时,将超出范围。 但r对外部范围仍然有效;因为它的范围更大,我们说 它 “活得更久”。如果 Rust 允许此代码工作,r将 引用在以下情况下释放的内存x已超出范围,并且 我们尝试做的任何事情r无法正常工作。那么 Rust 是如何做到的 确定此代码无效?它使用借用检查器。

借款检查器

Rust 编译器有一个借用检查器,可以比较范围以确定 是否所有借款均有效。示例 10-17 显示了与 清单 相同的代码 10-16,但带有显示变量生命周期的注释。

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

示例 10-17:生命周期的注释rx'a'b分别

在这里,我们注释了r'a以及x'b.如您所见,内部的'b块比外部的'alifetime 块。在编译时,Rust 会比较两者的大小 lifetimes 并看到r一生'a但它指的是记忆 一生'b.程序被拒绝是因为'b短于'a:参考文献的主题的寿命不如参考文献长。

示例 10-18 修复了代码,使其没有悬空的引用,并且 编译时没有任何错误。

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

示例 10-18:一个有效的引用,因为数据有一个 使用寿命比参考长

这里x具有生命周期'b,在本例中大于'a.这 方法rcan 引用x因为 Rust 知道r将 始终有效,而x有效。

现在你知道了什么是引用的生命周期以及 Rust 是如何分析的 lifetimes 来确保引用始终有效,让我们来探索一下 generic 函数上下文中参数和返回值的生命周期。

函数中的通用生命周期

我们将编写一个函数,返回两个字符串切片中较长的 one。这 function 将接受两个字符串切片并返回一个字符串切片。后 我们已经实施了longest函数,示例 10-19 中的代码应该 打印The longest string is abcd.

文件名: src/main.rs

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

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

示例 10-19:一个main函数调用longest函数来查找两个字符串切片中较长的

请注意,我们希望该函数采用字符串切片,即引用 而不是字符串,因为我们不希望longest函数 其参数的所有权。请参阅 “String Slices as Parameters“ 部分 有关为什么示例 10-19 中使用的参数是 我们想要的。

如果我们尝试实现longest函数,如 Example 10-20 所示,它 无法编译。

文件名: src/main.rs

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

示例 10-20:longest函数返回两个字符串切片中较长的 (LONGER),但尚未返回 编译

相反,我们得到以下关于生命周期的错误:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
 --> src/main.rs:9:33
  |
9 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

帮助文本显示返回类型需要一个通用的生命周期参数 因为 Rust 无法判断返回的引用是否引用了xy.实际上,我们也不知道,因为if块 返回对xelseblock 返回一个 参考y!

当我们定义这个函数时,我们不知道将 被传递到这个函数中,所以我们不知道ifcase 或elsecase 将执行。我们也不知道 引用,因此我们不能像在 清单 10-17 和 10-18 来确定我们返回的参考是否会 始终有效。借用检查器也无法确定这一点,因为它 不知道xy与 返回值。为了修复这个错误,我们将添加通用的生命周期参数,该参数 定义引用之间的关系,以便 Borrow Checker 可以 执行其分析。

生命周期注释语法

生命周期注释不会改变任何引用的生存时间。而 它们描述了对每个 其他的,而不会影响生命周期。就像函数可以接受任何类型 当 Signature 指定泛型类型参数时,函数可以接受 引用。

生命周期注解有一个稍微不常见的语法:生命周期的名称 参数必须以撇号 () 开头,并且通常都是小写的 并且非常短,就像泛型类型一样。大多数人使用这个名字''a对于第一个 lifetime 注释。我们将生命周期参数注释放在 的 之后 引用,使用空格将注释与引用的类型分隔开。&

以下是一些示例:对i32如果没有 lifetime 参数,则 对i32,它有一个名为'a和可变的 对i32这也具有'a.

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

一个生命周期注解本身没有太大意义,因为 注解旨在告诉 Rust 多个 参考资料彼此相关。让我们看看生命周期注解 在longest功能。

函数签名中的 Lifetime Annotations

要在函数签名中使用生命周期注解,我们需要声明 函数名称之间尖括号内的泛型生命周期参数 和 parameter 列表,就像我们对泛型类型参数所做的那样。

我们希望签名表达以下约束:返回的 只要两个参数都有效,reference 就是有效的。这是 参数的生命周期与返回值之间的关系。我们将 命名生命周期'a,然后将其添加到每个引用中,如 清单 中所示 10-21.

文件名: src/main.rs

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<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

示例 10-21:longest功能定义 指定签名中的所有引用必须具有相同的生命周期'a

此代码应该编译并生成我们想要的结果,当我们将其与main示例 10-19 中的 function 来访问。

函数签名现在告诉 Rust 在一段时间内'a中,函数 接受两个参数,这两个参数都是字符串切片,至少为 使用寿命长'a.函数签名还告诉 Rust 字符串 从函数返回的 slice 的寿命至少与 lifetime 一样长'a. 实际上,这意味着longestfunction 与值的生命周期中较小的值相同 由 function arguments 引用。这些关系正是我们想要的 Rust 在分析此代码时使用。

请记住,当我们在此函数签名中指定生命周期参数时, 我们不会更改传入或返回的任何值的生命周期。而 我们指定 Borrow 检查器应该拒绝任何不拒绝 遵守这些约束。请注意,longest函数不需要 确切了解多长时间xy会存活,只是某些范围可以 替代'a,这将满足此签名。

在函数中注释生命周期时,注释会进入函数 signature,而不是在函数体中。生命周期注解成为 函数的协定,与 Signature 中的类型非常相似。拥有 函数签名包含生命周期 Contract 表示分析 Rust 编译器可以更简单。如果函数的方式有问题 annotated 或调用方式,编译器错误可能指向 更准确地说,我们的代码和约束。如果 Rust 编译器 对我们打算如何对待 Lifetimes 的关系进行了更多的推断 为此,编译器可能只能指向使用我们的代码的许多步骤 远离问题的原因。

当我们将具体引用传递给longest,则具体生存期为 替代'a是 scope 的一部分x与 范围y.换句话说,泛型生命周期'a将得到混凝土 lifetime 等于 的 lifetime 中较小的xy.因为 我们使用相同的 lifetime 参数对返回的引用进行了注释'a, 返回的引用也将在 的生命周期xy.

让我们看看生命周期注解是如何限制longest函数 传入具有不同具体生命周期的引用。示例 10-22 是 一个简单的例子。

文件名: src/main.rs

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!("The longest string is {result}");
    }
}

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

示例 10-22:使用longest函数替换为 参考资料String具有不同具体生命周期的值

在此示例中,string1在外部范围结束之前有效,string2在内部范围结束之前有效,并且result引用某物 ,该 API 在内部范围结束之前有效。运行此代码,您将看到 借款检查器批准;它将编译并打印The longest string is long string is long.

接下来,让我们尝试一个例子,它显示result必须是两个参数的较小生存期。我们将 声明result变量,但保留 将值分配给result变量,其中string2.然后我们将println!使用result到外面 inner 作用域,在 inner 作用域结束后。示例 10-23 中的代码将 not compile 的

文件名: src/main.rs

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!("The longest string is {result}");
}

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

示例 10-23:尝试使用resultstring2已超出范围

当我们尝试编译此代码时,我们收到以下错误:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `string2` does not live long enough
 --> src/main.rs:6:44
  |
5 |         let string2 = String::from("xyz");
  |             ------- binding `string2` declared here
6 |         result = longest(string1.as_str(), string2.as_str());
  |                                            ^^^^^^^ borrowed value does not live long enough
7 |     }
  |     - `string2` dropped here while still borrowed
8 |     println!("The longest string is {result}");
  |                                     -------- borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

该错误显示,对于resultprintln!陈述string2需要有效到外部范围结束。Rust 知道 这是因为我们注解了函数参数的生命周期并返回 值使用相同的生命周期参数'a.

作为人类,我们可以查看此代码并看到string1长于string2,因此,result将包含对string1. 因为string1尚未超出范围,则对string1将 仍然对println!陈述。但是,编译器看不到 在这种情况下,引用是有效的。我们已经告诉 Rust 的生命周期 由longest函数与 传入的引用的生命周期。因此,借用检查器 不允许示例 10-23 中的代码可能具有无效的引用。

尝试设计更多实验,以改变 传递给longest函数以及返回的引用 被使用。假设您的实验是否会通过 编译前借用检查器;然后检查一下你是否正确!

从生命周期的角度思考

您需要指定生命周期参数的方式取决于您的 函数正在执行。例如,如果我们更改了longest函数始终返回第一个参数而不是最长的参数 string slice 中,我们不需要在y参数。这 以下代码将编译:

文件名: src/main.rs

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

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

我们指定了 lifetime 参数'a对于参数x和返回 type,但不用于参数y,因为y没有 任何具有生命周期x或返回值。

从函数返回引用时, return 类型需要与其中一个参数的 lifetime 参数匹配。如果 返回的引用引用其中一个参数,它必须引用 设置为在此函数中创建的值。然而,这将是一个悬而未决的问题 引用,因为该值将在函数结束时超出范围。 考虑一下longest不会 编译:

文件名: src/main.rs

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<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

在这里,即使我们指定了 lifetime 参数'a对于回报 type 时,该实现将编译失败,因为返回值 lifetime 与 parameters 的 lifetime 完全无关。这是 我们得到的错误消息:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0515]: cannot return value referencing local variable `result`
  --> src/main.rs:11:5
   |
11 |     result.as_str()
   |     ------^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `result` is borrowed here

For more information about this error, try `rustc --explain E0515`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error

问题是result超出范围并在最后被清理 的longest功能。我们还尝试返回对result从函数。我们无法指定 lifetime 参数 会更改悬空引用,并且 Rust 不允许我们创建一个悬空 参考。在这种情况下,最好的解决方法是返回拥有的数据类型 而不是引用,因此调用函数负责 清理值。

归根结底,生命周期语法是关于连接各种 函数的参数和返回值。一旦它们连接起来,Rust 就会 足够的信息来允许内存安全作并禁止 将创建悬空指针或以其他方式违反内存安全。

结构定义中的生命周期注释

到目前为止,我们定义的结构体都持有 owned 类型。我们可以定义结构体 来保存引用,但在这种情况下,我们需要添加一个 lifetime 注解 在结构体定义中的每个引用上。示例 10-24 有一个名为ImportantExcerpt,它包含一个字符串 slice。

文件名: src/main.rs

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

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

示例 10-24:一个包含引用的结构体,需要 生命周期注释

此结构体具有单个字段part,它包含一个字符串切片,它是一个 参考。与泛型数据类型一样,我们声明泛型 lifetime 参数放在结构体名称后面的尖括号内,这样我们就可以 在结构体定义的主体中使用 lifetime 参数。这 annotation 表示ImportantExcerpt不能超过引用 它保持其part田。

main函数在此处创建ImportantExcerpt结构 ,它引用了String由 变量novel.数据novel存在于ImportantExcerpt实例。另外novel直到 之后 这ImportantExcerpt超出范围,因此ImportantExcerpt实例有效。

终身省略

您已经了解到每个引用都有一个生命周期,您需要指定 使用引用的函数或结构的生命周期参数。但是,我们 在示例 4-9 中有一个函数,在示例 10-25 中再次显示,该函数编译了 没有生命周期注释。

文件名: src/lib.rs

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[..]
}

fn main() {
    let my_string = String::from("hello world");

    // first_word works on slices of `String`s
    let word = first_word(&my_string[..]);

    let my_string_literal = "hello world";

    // first_word works on slices of string literals
    let word = first_word(&my_string_literal[..]);

    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

示例 10-25:我们在示例 4-9 中定义的一个函数,该函数 编译时没有生命周期注解,即使参数和 return type 是引用

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

fn first_word<'a>(s: &'a str) -> &'a str {

在编写了大量 Rust 代码后,Rust 团队发现 Rust 程序员 特别是一遍又一遍地输入相同的生命周期注释 情况。这些情况是可预测的,并遵循一些确定性 模式。开发人员将这些模式编程到编译器的代码中,因此 在这些情况下,借款检查器可以推断生命周期,但不会 需要显式注释。

这段 Rust 历史是相关的,因为有可能更多的 确定性模式将出现并添加到编译器中。在未来, 可能需要更少的生命周期注释。

编程到 Rust 的引用分析中的模式称为生命周期省略规则。这些不是程序员应该遵循的规则;他们是 编译器将考虑的一组特定情况,以及如果您的代码 适合这些情况,则无需显式编写生命周期。

省略规则不提供完整的推理。如果仍然存在歧义,如 在 Rust 应用规则后引用的生命周期, 编译器不会猜测剩余引用的生命周期应该是多少。 编译器不会猜测,而是会给你一个你可以解决的错误 通过添加生命周期注释。

函数或方法参数的生命周期称为输入生命周期,而 返回值的生命周期称为 输出生命周期

编译器使用三个规则来计算引用的生命周期 当没有显式注释时。第一条规则适用于输入 生存期,第二个和第三个规则适用于输出生存期。如果 compiler 到达三个规则的末尾,并且仍然存在 它无法计算出生命周期,编译器将因错误而停止。 这些规则适用于fn定义以及impl块。

第一条规则是编译器为每个 参数作为引用。换句话说,具有一个参数的函数 获取一个 lifetime 参数:fn foo<'a>(x: &'a i32);一个具有两个 parameters 获取两个单独的生命周期参数:fn foo<'a, 'b>(x: &'a i32, y: &'b i32);等等。

第二条规则是,如果只有一个输入生命周期参数,则 lifetime 分配给所有输出 lifetime 参数:fn foo<'a>(x: &'a i32) -> &'a i32.

第三条规则是,如果有多个输入生命周期参数,但 其中之一是&self&mut self因为这是一个方法,所以self分配给所有输出生命周期参数。这第三条规则使 方法更易于读取和写入,因为需要的符号更少。

让我们假设我们是编译器。我们将应用这些规则来计算出 Signature 中引用的生命周期first_word函数 示例 10-25.签名开始时没有任何与 引用:

fn first_word(s: &str) -> &str {

然后,编译器应用第一条规则,该规则指定每个参数 获取自己的生命周期。我们称之为'a像往常一样,现在签名是 这:

fn first_word<'a>(s: &'a str) -> &str {

第二条规则适用,因为只有一个 input 生命周期。第二个 rule 指定将一个输入参数的生命周期分配给 output lifetime,因此签名现在是这样的:

fn first_word<'a>(s: &'a str) -> &'a str {

现在,此函数签名中的所有引用都具有生命周期,并且 编译器可以继续其分析,而无需程序员进行注释 此函数签名中的生命周期。

我们来看另一个示例,这次使用longest具有 在示例 10-20 中开始使用它时,没有生命周期参数:

fn longest(x: &str, y: &str) -> &str {

让我们应用第一条规则:每个参数都有自己的生命周期。这一次我们 有两个参数而不是一个,所以我们有两个生命周期:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

您可以看到第二条规则不适用,因为有多个 input 生命周期。第三条规则也不适用,因为longest是一个 function 而不是 method,因此所有参数都不是self.后 通过所有三条规则,我们仍然没有弄清楚回报是什么 type 的生命周期是。这就是为什么我们在尝试编译 示例 10-20:编译器完成了生命周期省略规则,但仍然 无法弄清楚签名中引用的所有生命周期。

因为第三条规则实际上只适用于方法签名,所以我们将查看 lifetimes 的 lifetimes,看看为什么第三条规则意味着我们不必这样做 经常在方法签名中注释生命周期。

方法定义中的 Lifetime 注解

当我们在具有生命周期的结构体上实现方法时,我们使用与 示例 10-11 中所示的泛型类型参数的 THAT。我们在哪里声明和 使用 lifetime 参数取决于它们是否与结构体相关 fields 或 method 参数和返回值。

结构体字段的生命周期名称始终需要在impl关键字,然后在结构体名称之后使用,因为这些生命周期是 的结构类型。

在方法签名中,在impl块中,引用可能会绑定到 结构体字段中引用的生命周期,或者它们可能是独立的。在 此外,生命周期省略规则通常使生命周期注释 在方法签名中不是必需的。我们来看一些使用 struct namedImportantExcerpt我们在示例 10-24 中定义的。

首先,我们将使用一个名为level其唯一参数是对self且其返回值为i32,它不是对任何内容的引用:

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

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    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().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

之后的生命周期参数声明impl及其在类型名称后的使用 是必需的,但我们不需要注释引用的生命周期 自self因为第一条省略规则。

下面是一个应用第三个生命周期省略规则的示例:

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

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    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().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

有两个 input 生命周期,因此 Rust 应用第一个生命周期省略规则 并给出两者&selfannouncement他们自己的一生。那么,因为 其中一个参数是&self,则返回类型获取&self, 所有的一生都被计算在内。

静态生命周期

我们需要讨论的一个特殊生命是'static,这表示 受影响的引用可以在程序的整个持续时间内存在。都 String 字面量具有'staticlifetime,我们可以按如下方式进行注释:

#![allow(unused)]
fn main() {
let s: &'static str = "I have a static lifetime.";
}

此字符串的文本直接存储在程序的二进制文件中,即 始终可用。因此,所有字符串文本的生命周期为'static.

您可能会看到使用'staticLIFE 错误消息。但 在指定'static作为生命周期以供参考,请考虑 您拥有的推荐人是否真的在您的整个生命周期中都存在 编程与否,以及您是否愿意。大多数情况下,错误消息 建议'staticlifetime 结果,方法是尝试创建 dangling 引用或可用生存期不匹配。在这种情况下,解决方案 是为了解决这些问题,而不是指定'static辈子。

泛型类型参数、trait bounds 和 lifetimes 一起

我们简单看一下指定泛型类型参数的语法,trait bounds 和 lifetimes 都在一个函数中!

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

    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Today is someone's birthday!",
    );
    println!("The longest string is {result}");
}

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!("Announcement! {ann}");
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

这是longest示例 10-21 中,返回 两个字符串切片。但现在它有一个名为ann泛型 类型T,该 API 可以由实现Displaytrait 中指定的where第。将打印此额外参数 使用 ,这就是为什么{}Displaytrait bound 是必需的。因为 lifetimes 是一种泛型,即 lifetime 参数'a和泛型类型参数T进入 angle 内的相同列表 函数名称后的方括号。

总结

我们在这一章中介绍了很多!现在您已经了解了泛型类型 parameters、traits 和 trait bounds,以及通用生命周期参数,那么您是 准备好编写在许多不同的情况下都能正常工作的无重复代码。 泛型类型参数允许您将代码应用于不同的类型。trait 和 trait bounds 确保即使类型是泛型的,它们也会具有 行为。您学习了如何使用生命周期注释来确保 这个灵活的代码不会有任何悬空的引用。而这一切 分析在编译时进行,这不会影响运行时性能!

信不信由你,关于我们讨论的主题,还有很多东西需要学习 本章:第 17 章讨论了 trait 对象,这是另一种使用 性状。还有更复杂的场景涉及生命周期注释 您仅在非常高级的场景中需要;对于这些,您应该阅读 Rust 参考。但接下来,您将学习如何在 Rust 这样你就可以确保你的代码按它应该的方式工作。

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准