使用 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 值。

外部范围声明一个命名没有初始值的变量,并且 内部范围声明一个以初始值为 .里面 在内部范围内,我们尝试将 的值设置为 作为对 .然后 内部作用域结束,我们尝试打印 中的值。此代码不会 compile 的 URL,因为所引用的值之前已超出范围 我们尝试使用它。错误消息如下:rx5rxrr

$ 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

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

借款检查器

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

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

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

在这里,我们注释了 with 的生存期和 with 的生存期。如您所见,内部块比外部生命周期块小得多。在编译时,Rust 会比较两者的大小 lifetimes 和 Sees 的生命周期为 但是它指的是内存 一生的 .程序被拒绝,因为 is short than : 引用的主题的寿命不如引用。r'ax'b'b'ar'a'b'b'a

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

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

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

这里, 的生命周期 ,在本例中大于 。这 means 可以引用,因为 Rust 知道 will 中的 always be valid while is valid.x'b'arxrx

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

函数中的通用生命周期

我们将编写一个函数,返回两个字符串切片中较长的 one。这 function 将接受两个字符串切片并返回一个字符串切片。后 我们已经实现了这个函数,示例 10-19 中的代码应该 打印。longestThe 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 函数,它调用最长的函数来找到两个字符串切片中较长的

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

如果我们尝试实现示例 10-20 所示的函数,它会 无法编译。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(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

示例 10-20:最长函数的实现,它返回两个字符串切片中较长的,但尚未返回 编译

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

$ 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 无法判断返回的引用是引用 还是 。其实我们也不知道,因为体内的块 的 this 函数返回对 的引用,并且该块返回 引用 !xyifxelsey

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

生命周期注释语法

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

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

以下是一些示例:对没有生命周期参数的引用、 对具有名为 的生命周期参数的 an 的引用,以及一个 mutable 引用 an 的 IF 中也具有 lifetime .i32i32'ai32'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 就是有效的。这是 参数的生命周期与返回值之间的关系。我们将 name 生命周期,然后将其添加到每个引用中,如 清单 中所示 10-21.'a

文件名: 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:最长的函数定义 指定签名中的所有引用必须具有相同的生命周期 'a

当我们将其与示例 10-19 中的函数一起使用时,这段代码应该编译并产生我们想要的结果。main

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

请记住,当我们在此函数签名中指定生命周期参数时, 我们不会更改传入或返回的任何值的生命周期。而 我们指定 Borrow 检查器应该拒绝任何不拒绝 遵守这些约束。请注意,该函数不需要 确切地知道会活多久,只是一些范围可以 替换为 that 将满足此签名。longestxy'a

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

当我们将具体引用传递给 时,具体生命周期为 substituted for 是范围中与 的范围。换句话说,泛型生命周期将获得具体的 lifetime 等于 和 的生命周期中较小的一个。因为 我们使用相同的 lifetime 参数 Comments 返回的引用 , 返回的引用也将在 和 的生命周期。longest'axy'axy'axy

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

文件名: 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 值的引用

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

接下来,让我们尝试一个例子,它表明 reference in 的生命周期必须是两个参数的较小生命周期。我们将 声明变量,但保留 将值赋值给作用域内的变量。然后我们将 that uses 移动到 inner 作用域,在 inner 作用域结束后。示例 10-23 中的代码将 not compile 的resultresultresultstring2println!result

文件名: 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:在 string2 超出范围后尝试使用 result

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

$ 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

该错误显示 for 对语句有效,需要在外部范围结束之前有效。Rust 知道 这是因为我们注解了函数参数的生命周期并返回 值 使用相同的 lifetime parameter 。resultprintln!string2'a

作为人类,我们可以查看此代码,发现它比 长,因此将包含对 的引用。 因为尚未超出范围,所以对 will 的引用 仍然对 statement 有效。但是,编译器看不到 在这种情况下,引用是有效的。我们已经告诉 Rust 的生命周期 函数返回的引用与 传入的引用的生命周期。因此,借用检查器 不允许示例 10-23 中的代码可能具有无效的引用。string1string2resultstring1string1string1println!longest

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

从生命周期的角度思考

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

文件名: 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 参数,并返回 type 的 URL,但不是 parameter ,因为 的生命周期没有 与 的生存期 或 返回值 的任何关系。'axyyx

从函数返回引用时, 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()
}

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

$ 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

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

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

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

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

文件名: 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:一个包含引用的结构体,需要 生命周期注释

这个结构体有一个字段,它包含一个字符串 slice,它是一个 参考。与泛型数据类型一样,我们声明泛型 lifetime 参数放在结构体名称后面的尖括号内,这样我们就可以 在结构体定义的主体中使用 lifetime 参数。这 annotation 表示 can't not longer the reference 的实例 它在自己的领域中站得住脚。partImportantExcerptpart

此处的函数创建 struct 的实例 ,它引用了 ownby 的 变量。中的数据在实例创建之前就已存在。此外,直到 超出范围,因此实例中的引用有效。mainImportantExcerptStringnovelnovelImportantExcerptnovelImportantExcerptImportantExcerpt

终身省略

您已经了解到每个引用都有一个生命周期,您需要指定 使用引用的函数或结构的生命周期参数。但是,我们 在示例 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 程序员 特别是一遍又一遍地输入相同的生命周期注释 情况。这些情况是可预测的,并遵循一些确定性 模式。开发人员将这些模式编程到编译器的代码中,因此 在这些情况下,Borrow Checker 可以推断生命周期,但不会 需要显式注释。

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

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

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

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

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

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

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

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

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

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 {

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

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

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

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

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

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

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

方法定义中的 Lifetime 注解

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

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

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

首先,我们将使用一个名为 namen 的方法,其唯一参数是 reference to ,其返回值是 an ,它不是对任何内容的引用:levelselfi32

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

生命周期参数声明 after 及其在类型名称后的使用 是必需的,但我们不需要注释引用的生命周期 to 的原因是第一个省略规则。implself

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

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'static

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

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

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

这是示例 10-21 中的函数,它返回 两个字符串切片。但现在它有一个额外的参数,名为 generic type ,该类型可由实现子句指定的特征的任何类型填充。将打印此额外参数 使用 ,这就是为什么需要 trait bound 的原因。因为 lifetimes 是一种泛型,lifetime 参数和泛型类型参数的声明在 angle 函数名称后的方括号。longestannTDisplaywhere{}Display'aT

总结

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

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

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