x-i18n: generated_at: “2026-03-01T14:08:28Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 8660fe043e6ca70645f6734f357dc61224ecd4e3454ebde7b696f89d6a0d6e91 source_path: ch10-03-lifetime-syntax.md workflow: 16
使用生命周期验证引用 (Validating References with Lifetimes)
Validating References with Lifetimes
生命周期 (Lifetimes) 是我们一直在使用的另一种泛型。生命周期并不确保类型具有我们想要的行为,而是确保引用在我们需要它们的时间内保持有效。
Lifetimes are another kind of generic that we’ve already been using. Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be.
我们在第 4 章“引用与借用”部分未讨论的一个细节是,Rust 中的每个引用都有一个生命周期,即该引用有效的范围。大多数时候,生命周期是隐式且推断出来的,就像大多数时候类型也是推断出来的一样。只有当多种类型可能时,我们才需要标注类型。类似地,当引用的生命周期可能以几种不同的方式相关联时,我们必须标注生命周期。Rust 要求我们使用泛型生命周期参数来标注这些关系,以确保运行时使用的实际引用肯定有效。
One detail we didn’t discuss in the “References and Borrowing” section in Chapter 4 is that every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We are only required to annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure that the actual references used at runtime will definitely be valid.
标注生命周期甚至不是大多数其他编程语言所具有的概念,因此这会让人感到陌生。虽然我们不会在本章涵盖生命周期的全部内容,但我们将讨论你可能遇到的常见生命周期语法,以便你熟悉这个概念。
Annotating lifetimes is not even a concept most other programming languages have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in their entirety in this chapter, we’ll discuss common ways you might encounter lifetime syntax so that you can get comfortable with the concept.
悬垂引用 (Dangling References)
Dangling References
生命周期的主要目的是防止“悬垂引用 (dangling references)”,如果允许它们存在,会导致程序引用非预期的内容。考虑示例 10-16 中的程序,它有一个外部作用域和一个内部作用域。
The main aim of lifetimes is to prevent dangling references, which, if they were allowed to exist, would cause a program to reference data other than the data it’s intended to reference. Consider the program in Listing 10-16, which has an outer scope and an inner scope.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/src/main.rs}}
注意:示例 10-16、10-17 和 10-23 声明了变量但未赋予初始值,因此变量名存在于外部作用域中。乍一看,这似乎与 Rust 没有 null 值相冲突。然而,如果我们尝试在给变量赋值之前使用它,我们会得到一个编译时错误,这表明 Rust 确实不允许 null 值。
Note: The examples in Listings 10-16, 10-17, and 10-23 declare variables without giving them an initial value, so the variable name exists in the outer scope. At first glance, this might appear to be in conflict with Rust having no null values. However, if we try to use a variable before giving it a value, we’ll get a compile-time error, which shows that indeed Rust does not allow null values.
外部作用域声明了一个名为 r 的变量且没有初始值,内部作用域声明了一个名为 x 的变量且初始值为 5。在内部作用域中,我们尝试将 r 的值设置为对 x 的引用。然后内部作用域结束,我们尝试打印 r 中的值。这段代码无法编译,因为 r 所引用的值在我们尝试使用它之前就已经超出了作用域。以下是错误消息:
The outer scope declares a variable named r with no initial value, and the
inner scope declares a variable named x with the initial value of 5. Inside
the inner scope, we attempt to set the value of r as a reference to x.
Then, the inner scope ends, and we attempt to print the value in r. This code
won’t compile, because the value that r is referring to has gone out of scope
before we try to use it. Here is the error message:
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/output.txt}}
错误消息说变量 x “活得不够久 (does not live long enough)”。原因是当内部作用域在第 7 行结束时,x 将超出作用域。但 r 对外部作用域仍然有效;因为它的作用域更大,我们说它“活得更久”。如果 Rust 允许这段代码运行,r 将引用在 x 超出作用域时已被释放的内存,我们尝试对 r 做的任何事情都无法正常工作。那么,Rust 是如何确定这段代码无效的呢?它使用了借用检查器。
The error message says that the variable x “does not live long enough.” The
reason is that x will be out of scope when the inner scope ends on line 7.
But r is still valid for the outer scope; because its scope is larger, we say
that it “lives longer.” If Rust allowed this code to work, r would be
referencing memory that was deallocated when x went out of scope, and
anything we tried to do with r wouldn’t work correctly. So, how does Rust
determine that this code is invalid? It uses a borrow checker.
借用检查器 (The Borrow Checker)
The Borrow Checker
Rust 编译器有一个“借用检查器 (borrow checker)”,它通过比较作用域来确定所有的借用是否有效。示例 10-17 显示了与示例 10-16 相同的代码,但带有显示变量生命周期的标注。
The Rust compiler has a borrow checker that compares scopes to determine whether all borrows are valid. Listing 10-17 shows the same code as Listing 10-16 but with annotations showing the lifetimes of the variables.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-17/src/main.rs}}
在这里,我们将 r 的生命周期标注为 'a,将 x 的生命周期标注为 'b。如你所见,内部的 'b 块比外部的 'a 生命周期块小得多。在编译时,Rust 比较这两个生命周期的大小,发现 r 具有 'a 的生命周期,但它引用了具有 'b 生命周期的内存。程序被拒绝了,因为 'b 比 'a 短:引用的对象没有引用活得长。
Here, we’ve annotated the lifetime of r with 'a and the lifetime of x
with 'b. As you can see, the inner 'b block is much smaller than the outer
'a lifetime block. At compile time, Rust compares the size of the two
lifetimes and sees that r has a lifetime of 'a but that it refers to memory
with a lifetime of 'b. The program is rejected because 'b is shorter than
'a: The subject of the reference doesn’t live as long as the reference.
示例 10-18 修复了代码,使其不再具有悬垂引用,并且可以无错编译。
Listing 10-18 fixes the code so that it doesn’t have a dangling reference and it compiles without any errors.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-18/src/main.rs}}
}
在这里,x 具有生命周期 'b,在此例中它比 'a 大。这意味着 r 可以引用 x,因为 Rust 知道只要 x 有效,r 中的引用就始终有效。
Here, x has the lifetime 'b, which in this case is larger than 'a. This
means r can reference x because Rust knows that the reference in r will
always be valid while x is valid.
既然你已经知道了引用的生命周期在哪里,以及 Rust 如何分析生命周期以确保引用始终有效,让我们来探索函数参数和返回值中的泛型生命周期。
Now that you know where the lifetimes of references are and how Rust analyzes lifetimes to ensure that references will always be valid, let’s explore generic lifetimes in function parameters and return values.
函数中的泛型生命周期 (Generic Lifetimes in Functions)
Generic Lifetimes in Functions
我们将编写一个函数,返回两个字符串切片中较长的一个。该函数将接收两个字符串切片并返回一个字符串切片。在我们实现了 longest 函数后,示例 10-19 中的代码应该打印 The longest string is abcd。
We’ll write a function that returns the longer of two string slices. This
function will take two string slices and return a single string slice. After
we’ve implemented the longest function, the code in Listing 10-19 should
print The longest string is abcd.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-19/src/main.rs}}
请注意,我们希望该函数接收字符串切片(即引用)而不是字符串,因为我们不希望 longest 函数获取其参数的所有权。关于为什么我们在示例 10-19 中使用的参数是我们想要的,请参阅第 4 章中的“字符串切片作为参数”进行更多讨论。
Note that we want the function to take string slices, which are references,
rather than strings, because we don’t want the longest function to take
ownership of its parameters. Refer to “String Slices as
Parameters” in Chapter 4 for more
discussion about why the parameters we use in Listing 10-19 are the ones we
want.
如果我们尝试按照示例 10-20 所示实现 longest 函数,它将无法通过编译。
If we try to implement the longest function as shown in Listing 10-20, it
won’t compile.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-20/src/main.rs:here}}
相反,我们得到了以下涉及生命周期的错误:
Instead, we get the following error that talks about lifetimes:
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-20/output.txt}}
帮助文本揭示了返回类型需要一个泛型生命周期参数,因为 Rust 无法判断返回的引用是引用自 x 还是 y。实际上,我们也不知道,因为该函数体内的 if 块返回了对 x 的引用,而 else 块返回了对 y 的引用!
The help text reveals that the return type needs a generic lifetime parameter
on it because Rust can’t tell whether the reference being returned refers to
x or y. Actually, we don’t know either, because the if block in the body
of this function returns a reference to x and the else block returns a
reference to y!
当我们定义此函数时,我们不知道将传入该函数的具体值,因此我们不知道是 if 情况还是 else 情况会执行。我们也不知道传入的引用的具体生命周期,因此我们不能像在示例 10-17 和 10-18 中那样查看作用域来确定我们返回的引用是否始终有效。借用检查器也无法确定这一点,因为它不知道 x 和 y 的生命周期如何与返回值的生命周期相关联。为了修复此错误,我们将添加定义引用之间关系的泛型生命周期参数,以便借用检查器可以执行其分析。
When we’re defining this function, we don’t know the concrete values that will
be passed into this function, so we don’t know whether the if case or the
else case will execute. We also don’t know the concrete lifetimes of the
references that will be passed in, so we can’t look at the scopes as we did in
Listings 10-17 and 10-18 to determine whether the reference we return will
always be valid. The borrow checker can’t determine this either, because it
doesn’t know how the lifetimes of x and y relate to the lifetime of the
return value. To fix this error, we’ll add generic lifetime parameters that
define the relationship between the references so that the borrow checker can
perform its analysis.
生命周期标注语法 (Lifetime Annotation Syntax)
Lifetime Annotation Syntax
生命周期标注并不改变任何引用的存活时间。相反,它们描述了多个引用的生命周期彼此之间的关系,而不影响其生命周期。就像函数在签名指定泛型类型参数时可以接受任何类型一样,函数通过指定泛型生命周期参数可以接受任何生命周期的引用。
Lifetime annotations don’t change how long any of the references live. Rather, they describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter.
生命周期标注有一种稍微不寻常的语法:生命周期参数的名称必须以单引号 (') 开头,并且通常全小写且非常短,类似于泛型类型。大多数人使用名称 'a 作为第一个生命周期标注。我们将生命周期参数标注放在引用的 & 之后,使用空格将标注与引用的类型隔开。
Lifetime annotations have a slightly unusual syntax: The names of lifetime
parameters must start with an apostrophe (') and are usually all lowercase
and very short, like generic types. Most people use the name 'a for the first
lifetime annotation. We place lifetime parameter annotations after the & of a
reference, using a space to separate the annotation from the reference’s type.
这里有一些例子:一个没有生命周期参数的 i32 引用,一个具有名为 'a 的生命周期参数的 i32 引用,以及一个同样具有生命周期 'a 的可变 i32 引用:
Here are some examples—a reference to an i32 without a lifetime parameter, a
reference to an i32 that has a lifetime parameter named 'a, and a mutable
reference to an i32 that also has the lifetime 'a:
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
单个生命周期标注本身并没有太大的意义,因为标注是为了告诉 Rust 多个引用的泛型生命周期参数如何相互关联。让我们在 longest 函数的背景下检查生命周期标注是如何相互关联的。
One lifetime annotation by itself doesn’t have much meaning, because the
annotations are meant to tell Rust how generic lifetime parameters of multiple
references relate to each other. Let’s examine how the lifetime annotations
relate to each other in the context of the longest function.
在函数签名中 (In Function Signatures)
In Function Signatures
要在函数签名中使用生命周期标注,我们需要像对泛型类型参数所做的那样,在函数名和参数列表之间的尖括号内声明泛型生命周期参数。
To use lifetime annotations in function signatures, we need to declare the generic lifetime parameters inside angle brackets between the function name and the parameter list, just as we did with generic type parameters.
我们希望签名能表达以下约束:返回的引用只要两个参数都有效,它就有效。这是参数和返回值生命周期之间的关系。我们将生命周期命名为 'a,然后将其添加到每个引用中,如示例 10-21 所示。
We want the signature to express the following constraint: The returned
reference will be valid as long as both of the parameters are valid. This is
the relationship between lifetimes of the parameters and the return value.
We’ll name the lifetime 'a and then add it to each reference, as shown in
Listing 10-21.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-21/src/main.rs:here}}
}
这段代码应该可以编译,并且在与示例 10-19 中的 main 函数一起使用时产生我们想要的结果。
This code should compile and produce the result we want when we use it with the
main function in Listing 10-19.
现在的函数签名告诉 Rust:对于某种生命周期 'a,该函数接收两个参数,它们都是至少与生命周期 'a 活得一样长的字符串切片。函数签名还告诉 Rust,该函数返回的字符串切片也将至少与生命周期 'a 活得一样长。在实践中,这意味着由 longest 函数返回的引用的生命周期与函数参数所引用的值的生命周期中较小的一个相同。这些关系正是我们希望 Rust 在分析此代码时使用的。
The function signature now tells Rust that for some lifetime 'a, the function
takes two parameters, both of which are string slices that live at least as
long as lifetime 'a. The function signature also tells Rust that the string
slice returned from the function will live at least as long as lifetime 'a.
In practice, it means that the lifetime of the reference returned by the
longest function is the same as the smaller of the lifetimes of the values
referred to by the function arguments. These relationships are what we want
Rust to use when analyzing this code.
请记住,当我们在此函数签名中指定生命周期参数时,我们并没有改变任何传入或返回值的生命周期。相反,我们是在指定借用检查器应该拒绝任何不遵守这些约束的值。请注意,longest 函数不需要知道 x 和 y 到底会活多久,只需要知道可以将某个作用域代入 'a 且能满足此签名即可。
Remember, when we specify the lifetime parameters in this function signature,
we’re not changing the lifetimes of any values passed in or returned. Rather,
we’re specifying that the borrow checker should reject any values that don’t
adhere to these constraints. Note that the longest function doesn’t need to
know exactly how long x and y will live, only that some scope can be
substituted for 'a that will satisfy this signature.
在为函数标注生命周期时,标注位于函数签名中,而不是函数体中。生命周期标注成为了函数合同的一部分,就像签名中的类型一样。让函数签名包含生命周期合同意味着 Rust 编译器所做的分析可以更简单。如果函数标注的方式或其调用的方式存在问题,编译器错误可以更精确地指向我们的代码部分和约束。相反,如果 Rust 编译器对我们预期的生命周期关系进行了更多推断,编译器可能只能指向距离问题原因许多步之外的我们的代码使用处。
When annotating lifetimes in functions, the annotations go in the function signature, not in the function body. The lifetime annotations become part of the contract of the function, much like the types in the signature. Having function signatures contain the lifetime contract means the analysis the Rust compiler does can be simpler. If there’s a problem with the way a function is annotated or the way it is called, the compiler errors can point to the part of our code and the constraints more precisely. If, instead, the Rust compiler made more inferences about what we intended the relationships of the lifetimes to be, the compiler might only be able to point to a use of our code many steps away from the cause of the problem.
当我们向 longest 传递具体的引用时,代入 'a 的具体生命周期是 x 的作用域中与 y 的作用域重叠的部分。换句话说,泛型生命周期 'a 将获得等于 x 和 y 生命周期中较小者的具体生命周期。因为我们为返回的引用标注了相同的生命周期参数 'a,所以返回的引用在 x 和 y 生命周期中较小者的长度内也将是有效的。
When we pass concrete references to longest, the concrete lifetime that is
substituted for 'a is the part of the scope of x that overlaps with the
scope of y. In other words, the generic lifetime 'a will get the concrete
lifetime that is equal to the smaller of the lifetimes of x and y. Because
we’ve annotated the returned reference with the same lifetime parameter 'a,
the returned reference will also be valid for the length of the smaller of the
lifetimes of x and y.
让我们通过传入具有不同具体生命周期的引用,来看看生命周期标注是如何限制 longest 函数的。示例 10-22 是一个直观的例子。
Let’s look at how the lifetime annotations restrict the longest function by
passing in references that have different concrete lifetimes. Listing 10-22 is
a straightforward example.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-22/src/main.rs:here}}
}
在这个例子中,string1 在外部作用域结束前有效,string2 在内部作用域结束前有效,而 result 引用了在内部作用域结束前有效的东西。运行此代码,你会看到借用检查器批准了它;它将编译并打印 The longest string is long string is long。
In this example, string1 is valid until the end of the outer scope, string2
is valid until the end of the inner scope, and result references something
that is valid until the end of the inner scope. Run this code and you’ll see
that the borrow checker approves; it will compile and print The longest string is long string is long.
接下来,让我们尝试一个例子,展示 result 中引用的生命周期必须是两个参数中较小的那个生命周期。我们将 result 变量的声明移动到内部作用域之外,但保持在内部作用域中对 result 变量进行 string2 值的赋值。然后,我们将使用 result 的 println! 移动到内部作用域之外,即内部作用域结束之后。示例 10-23 中的代码将无法编译。
Next, let’s try an example that shows that the lifetime of the reference in
result must be the smaller lifetime of the two arguments. We’ll move the
declaration of the result variable outside the inner scope but leave the
assignment of the value to the result variable inside the scope with
string2. Then, we’ll move the println! that uses result to outside the
inner scope, after the inner scope has ended. The code in Listing 10-23 will
not compile.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/src/main.rs:here}}
当我们尝试编译这段代码时,我们得到了这个错误:
When we try to compile this code, we get this error:
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/output.txt}}
错误表明为了使 result 在 println! 语句中有效,string2 需要一直有效直到外部作用域结束。Rust 知道这一点,因为我们使用相同的生命周期参数 'a 标注了函数参数和返回值的生命周期。
The error shows that for result to be valid for the println! statement,
string2 would need to be valid until the end of the outer scope. Rust knows
this because we annotated the lifetimes of the function parameters and return
values using the same lifetime parameter 'a.
作为人类,我们可以观察这段代码并看到 string1 比 string2 长,因此 result 将包含一个指向 string1 的引用。由于 string1 尚未超出作用域,对 string1 的引用在 println! 语句中仍将有效。然而,编译器在这种情况下无法看到引用是有效的。我们告诉 Rust,由 longest 函数返回的引用的生命周期与传入引用的生命周期中较小的一个相同。因此,借用检查器不允许示例 10-23 中的代码,认为它可能具有无效引用。
As humans, we can look at this code and see that string1 is longer than
string2, and therefore, result will contain a reference to string1.
Because string1 has not gone out of scope yet, a reference to string1 will
still be valid for the println! statement. However, the compiler can’t see
that the reference is valid in this case. We’ve told Rust that the lifetime of
the reference returned by the longest function is the same as the smaller of
the lifetimes of the references passed in. Therefore, the borrow checker
disallows the code in Listing 10-23 as possibly having an invalid reference.
尝试设计更多实验,改变传递给 longest 函数的引用的值和生命周期,以及如何使用返回的引用。在编译之前,先假设你的实验是否能通过借用检查器;然后检查你是否正确!
Try designing more experiments that vary the values and lifetimes of the
references passed in to the longest function and how the returned reference
is used. Make hypotheses about whether or not your experiments will pass the
borrow checker before you compile; then, check to see if you’re right!
关系 (Relationships)
Relationships
指定生命周期参数的方式取决于你的函数在做什么。例如,如果我们更改 longest 函数的实现,使其始终返回第一个参数而不是最长的字符串切片,我们就不需要在 y 参数上指定生命周期。以下代码将可以编译:
The way in which you need to specify lifetime parameters depends on what your
function is doing. For example, if we changed the implementation of the
longest function to always return the first parameter rather than the longest
string slice, we wouldn’t need to specify a lifetime on the y parameter. The
following code will compile:
文件名: src/main.rs
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-08-only-one-reference-with-lifetime/src/main.rs:here}}
}
我们为参数 x 和返回类型指定了生命周期参数 'a,但没有为参数 y 指定,因为 y 的生命周期与 x 的生命周期或返回值没有任何关系。
We’ve specified a lifetime parameter 'a for the parameter x and the return
type, but not for the parameter y, because the lifetime of y does not have
any relationship with the lifetime of x or the return value.
从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期参数匹配。如果返回的引用“不”引用其中的一个参数,那么它必须引用在此函数内部创建的一个值。然而,这将是一个悬垂引用,因为该值在函数结束时将超出作用域。考虑这个尝试实现 longest 函数但无法编译的例子:
When returning a reference from a function, the lifetime parameter for the
return type needs to match the lifetime parameter for one of the parameters. If
the reference returned does not refer to one of the parameters, it must refer
to a value created within this function. However, this would be a dangling
reference because the value will go out of scope at the end of the function.
Consider this attempted implementation of the longest function that won’t
compile:
文件名: src/main.rs
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-09-unrelated-lifetime/src/main.rs:here}}
在这里,即使我们为返回类型指定了生命周期参数 'a,这个实现也会编译失败,因为返回值的生命周期与参数的生命周期完全没有关系。这是我们得到的错误消息:
Here, even though we’ve specified a lifetime parameter 'a for the return
type, this implementation will fail to compile because the return value
lifetime is not related to the lifetime of the parameters at all. Here is the
error message we get:
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-09-unrelated-lifetime/output.txt}}
问题在于 result 在 longest 函数结束时超出作用域并被清理。而我们还尝试从函数中返回 result 的引用。我们没有办法指定能够改变悬垂引用的生命周期参数,而且 Rust 不允许我们创建悬垂引用。在这种情况下,最好的修复方法是返回一个拥有所有权的数据类型而不是引用,这样调用函数就负责清理该值。
The problem is that result goes out of scope and gets cleaned up at the end
of the longest function. We’re also trying to return a reference to result
from the function. There is no way we can specify lifetime parameters that
would change the dangling reference, and Rust won’t let us create a dangling
reference. In this case, the best fix would be to return an owned data type
rather than a reference so that the calling function is then responsible for
cleaning up the value.
归根结底,生命周期语法是关于连接函数的各个参数和返回值的生命周期的。一旦它们连接起来,Rust 就有了足够的信息来允许内存安全的操作,并禁止会产生悬垂指针或以其他方式违反内存安全的操作。
Ultimately, lifetime syntax is about connecting the lifetimes of various parameters and return values of functions. Once they’re connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety.
在结构体定义中 (In Struct Definitions)
In Struct Definitions
到目前为止,我们定义的结构体都持有拥有所有权的类型。我们可以定义持有引用的结构体,但在这种情况下,我们需要在结构体定义的每个引用上添加生命周期标注。示例 10-24 有一个名为 ImportantExcerpt 的结构体,它持有一个字符串切片。
So far, the structs we’ve defined all hold owned types. We can define structs
to hold references, but in that case, we would need to add a lifetime
annotation on every reference in the struct’s definition. Listing 10-24 has a
struct named ImportantExcerpt that holds a string slice.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-24/src/main.rs}}
}
该结构体只有一个 part 字段,它持有一个字符串切片,这是一个引用。与泛型数据类型一样,我们在结构体名称后的尖括号内声明泛型生命周期参数的名称,以便我们可以在结构体定义的主体中使用该生命周期参数。此标注意味着 ImportantExcerpt 实例的存活时间不能超过它在 part 字段中持有的引用。
This struct has the single field part that holds a string slice, which is a
reference. As with generic data types, we declare the name of the generic
lifetime parameter inside angle brackets after the name of the struct so that
we can use the lifetime parameter in the body of the struct definition. This
annotation means an instance of ImportantExcerpt can’t outlive the reference
it holds in its part field.
这里的 main 函数创建了一个 ImportantExcerpt 结构体的实例,它持有一个指向变量 novel 所有的 String 第一句的引用。novel 中的数据在 ImportantExcerpt 实例创建之前就存在了。此外,novel 直到 ImportantExcerpt 超出作用域后才超出作用域,因此 ImportantExcerpt 实例中的引用是有效的。
The main function here creates an instance of the ImportantExcerpt struct
that holds a reference to the first sentence of the String owned by the
variable novel. The data in novel exists before the ImportantExcerpt
instance is created. In addition, novel doesn’t go out of scope until after
the ImportantExcerpt goes out of scope, so the reference in the
ImportantExcerpt instance is valid.
生命周期省略 (Lifetime Elision)
Lifetime Elision
你已经了解到每个引用都有一个生命周期,并且你需要为使用引用的函数或结构体指定生命周期参数。然而,我们在示例 4-9 中有一个函数(示例 10-25 中再次显示),它在没有生命周期标注的情况下通过了编译。
You’ve learned that every reference has a lifetime and that you need to specify lifetime parameters for functions or structs that use references. However, we had a function in Listing 4-9, shown again in Listing 10-25, that compiled without lifetime annotations.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-25/src/main.rs:here}}
}
此函数在没有生命周期标注的情况下可以编译的原因是历史性的:在 Rust 的早期版本(1.0 之前)中,这段代码无法编译,因为每个引用都需要一个显式的生命周期。当时,函数签名会这样写:
The reason this function compiles without lifetime annotations is historical: In early versions (pre-1.0) of Rust, this code wouldn’t have compiled, because every reference needed an explicit lifetime. At that time, the function signature would have been written like this:
fn first_word<'a>(s: &'a str) -> &'a str {
在编写了大量 Rust 代码后,Rust 团队发现 Rust 程序员在特定情况下会反复输入相同的生命周期标注。这些情况是可以预见的,并且遵循一些确定的模式。开发人员将这些模式编入编译器代码中,以便借用检查器在这些情况下可以推断生命周期,而不需要显式的标注。
After writing a lot of Rust code, the Rust team found that Rust programmers were entering the same lifetime annotations over and over in particular situations. These situations were predictable and followed a few deterministic patterns. The developers programmed these patterns into the compiler’s code so that the borrow checker could infer the lifetimes in these situations and wouldn’t need explicit annotations.
这段 Rust 的历史非常相关,因为将来可能会出现更多的确定性模式并被添加到编译器中。将来,可能需要更少的生命周期标注。
This piece of Rust history is relevant because it’s possible that more deterministic patterns will emerge and be added to the compiler. In the future, even fewer lifetime annotations might be required.
被编入 Rust 对引用分析的模式被称为“生命周期省略规则 (lifetime elision rules)”。这些不是程序员要遵循的规则;它们是编译器会考虑的一组特定情况,如果你的代码符合这些情况,你就不需要显式地写出生命周期。
The patterns programmed into Rust’s analysis of references are called the lifetime elision rules. These aren’t rules for programmers to follow; they’re a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly.
省略规则并不提供完全的推断。如果在 Rust 应用了规则之后,引用的生命周期仍然存在歧义,编译器将不会猜测剩余引用的生命周期应该是什么。编译器不会猜测,而是会给你一个错误,你可以通过添加生命周期标注来解决该错误。
The elision rules don’t provide full inference. If there is still ambiguity about what lifetimes the references have after Rust applies the rules, the compiler won’t guess what the lifetime of the remaining references should be. Instead of guessing, the compiler will give you an error that you can resolve by adding the lifetime annotations.
函数或方法参数上的生命周期被称为“输入生命周期 (input lifetimes)”,而返回值上的生命周期被称为“输出生命周期 (output lifetimes)”。
Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.
编译器使用三条规则来在没有显式标注时确定引用的生命周期。第一条规则适用于输入生命周期,第二条和第三条规则适用于输出生命周期。如果编译器在应用完这三条规则后,仍有无法确定生命周期的引用,编译器将报错停止。这些规则适用于 fn 定义以及 impl 块。
The compiler uses three rules to figure out the lifetimes of the references
when there aren’t explicit annotations. The first rule applies to input
lifetimes, and the second and third rules apply to output lifetimes. If the
compiler gets to the end of the three rules and there are still references for
which it can’t figure out lifetimes, the compiler will stop with an error.
These rules apply to fn definitions as well as impl blocks.
第一条规则是编译器为每个作为引用的参数分配一个生命周期参数。换句话说,具有一个参数的函数获得一个生命周期参数:fn foo<'a>(x: &'a i32);具有两个参数的函数获得两个独立的生命周期参数:fn foo<'a, 'b>(x: &'a i32, y: &'b i32);依此类推。
The first rule is that the compiler assigns a lifetime parameter to each
parameter that’s a reference. In other words, a function with one parameter
gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two
parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.
第二条规则是,如果恰好只有一个输入生命周期参数,该生命周期将被分配给所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32。
The second rule is that, if there is exactly one input lifetime parameter, that
lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.
第三条规则是,如果有多个输入生命周期参数,但其中一个是 &self 或 &mut self(因为这是个方法),则 self 的生命周期被分配给所有输出生命周期参数。这第三条规则使得方法读写起来更加舒心,因为需要的符号更少。
The third rule is that, if there are multiple input lifetime parameters, but
one of them is &self or &mut self because this is a method, the lifetime of
self is assigned to all output lifetime parameters. This third rule makes
methods much nicer to read and write because fewer symbols are necessary.
让我们假装自己是编译器。我们将应用这些规则来确定示例 10-25 中 first_word 函数签名中的引用生命周期。签名最初没有任何与引用关联的生命周期:
Let’s pretend we’re the compiler. We’ll apply these rules to figure out the
lifetimes of the references in the signature of the first_word function in
Listing 10-25. The signature starts without any lifetimes associated with the
references:
fn first_word(s: &str) -> &str {
然后编译器应用第一条规则,该规则指定每个参数获得其自己的生命周期。像往常一样,我们称之为 'a ,所以现在签名是这样的:
fn first_word<'a>(s: &'a str) -> &str {
第二条规则适用,因为恰好有一个输入生命周期。第二条规则指定那一个输入参数的生命周期被分配给输出生命周期,所以现在签名是这样的:
fn first_word<'a>(s: &'a str) -> &'a str {
现在该函数签名中的所有引用都具有了生命周期,编译器可以继续其分析,而不需要程序员在函数签名中标注生命周期。
Now all the references in this function signature have lifetimes, and the compiler can continue its analysis without needing the programmer to annotate the lifetimes in this function signature.
让我们看另一个例子,这次使用示例 10-20 中我们在开始处理它时没有生命周期参数的 longest 函数:
Let’s look at another example, this time using the longest function that had
no lifetime parameters when we started working with it in Listing 10-20:
fn longest(x: &str, y: &str) -> &str {
让我们应用第一条规则:每个参数获得其自己的生命周期。这一次我们有两个参数而不是一个,所以我们有两个生命周期:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
你可以看到第二条规则不适用,因为输入生命周期不止一个。第三条规则也不适用,因为 longest 是一个函数而不是一个方法,所以参数中没有 self。在处理完所有三条规则后,我们仍然没有弄清楚返回类型的生命周期是什么。这就是为什么我们在尝试编译示例 10-20 中的代码时得到了错误:编译器处理了生命周期省略规则,但仍然无法确定签名中引用的所有生命周期。
You can see that the second rule doesn’t apply, because there is more than one
input lifetime. The third rule doesn’t apply either, because longest is a
function rather than a method, so none of the parameters are self. After
working through all three rules, we still haven’t figured out what the return
type’s lifetime is. This is why we got an error trying to compile the code in
Listing 10-20: The compiler worked through the lifetime elision rules but still
couldn’t figure out all the lifetimes of the references in the signature.
因为第三条规则实际上只适用于方法签名,接下来我们将看看该语境下的生命周期,以了解为什么第三条规则意味着我们不需要经常在方法签名中标注生命周期。
Because the third rule really only applies in method signatures, we’ll look at lifetimes in that context next to see why the third rule means we don’t have to annotate lifetimes in method signatures very often.
在方法定义中 (In Method Definitions)
In Method Definitions
当我们在带有生命周期的结构体上实现方法时,我们使用与泛型类型参数相同的语法,如示例 10-11 所示。我们在何处声明和使用生命周期参数取决于它们是与结构体字段相关,还是与方法参数和返回值相关。
When we implement methods on a struct with lifetimes, we use the same syntax as that of generic type parameters, as shown in Listing 10-11. Where we declare and use the lifetime parameters depends on whether they’re related to the struct fields or the method parameters and return values.
结构体字段的生命周期名称始终需要在 impl 关键字之后声明,然后在结构体名称之后使用,因为这些生命周期是结构体类型的一部分。
Lifetime names for struct fields always need to be declared after the impl
keyword and then used after the struct’s name because those lifetimes are part
of the struct’s type.
在 impl 块内部的方法签名中,引用可能与结构体字段中引用的生命周期绑定,也可能是独立的。此外,生命周期省略规则经常使得方法签名中不需要生命周期标注。让我们看一些使用我们在示例 10-24 中定义的名为 ImportantExcerpt 的结构体的例子。
In method signatures inside the impl block, references might be tied to the
lifetime of references in the struct’s fields, or they might be independent. In
addition, the lifetime elision rules often make it so that lifetime annotations
aren’t necessary in method signatures. Let’s look at some examples using the
struct named ImportantExcerpt that we defined in Listing 10-24.
首先,我们将使用一个名为 level 的方法,其唯一参数是对 self 的引用,其返回值是一个 i32,它不是对任何东西的引用:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-10-lifetimes-on-methods/src/main.rs:1st}}
}
impl 之后的生命周期参数声明及其在类型名称之后的引用是必需的,但由于第一条省略规则,我们不被要求标注 self 引用的生命周期。
The lifetime parameter declaration after impl and its use after the type name
are required, but because of the first elision rule, we’re not required to
annotate the lifetime of the reference to self.
这是一个适用第三条生命周期省略规则的例子:
Here is an example where the third lifetime elision rule applies:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-10-lifetimes-on-methods/src/main.rs:3rd}}
}
有两个输入生命周期,因此 Rust 应用第一条生命周期省略规则,并为 &self 和 announcement 都分配它们自己的生命周期。然后,因为其中一个参数是 &self ,返回类型就获得了 &self 的生命周期,所有的生命周期都得到了解决。
There are two input lifetimes, so Rust applies the first lifetime elision rule
and gives both &self and announcement their own lifetimes. Then, because
one of the parameters is &self, the return type gets the lifetime of &self,
and all lifetimes have been accounted for.
静态生命周期 (The Static Lifetime)
The Static Lifetime
我们需要讨论的一个特殊生命周期是 'static,它表示受影响的引用“可以”在程序的整个持续时间内有效。所有的字符串字面量都具有 'static 生命周期,我们可以按如下方式标注:
One special lifetime we need to discuss is 'static, which denotes that the
affected reference can live for the entire duration of the program. All
string literals have the 'static lifetime, which we can annotate as follows:
#![allow(unused)]
fn main() {
let s: &'static str = "I have a static lifetime.";
}
该字符串的文本直接存储在程序的可执行文件中,它是始终可用的。因此,所有字符串字面量的生命周期都是 'static。
The text of this string is stored directly in the program’s binary, which is
always available. Therefore, the lifetime of all string literals is 'static.
你可能会在错误消息中看到使用 'static 生命周期的建议。但在将 'static 指定为引用的生命周期之前,请思考你拥有的引用是否真的存活于程序的整个生命周期,以及你是否希望它如此。大多数时候,建议使用 'static 生命周期的错误消息是源于尝试创建悬垂引用或可用生命周期不匹配。在这些情况下,解决方案是修复这些问题,而不是指定 'static 生命周期。
You might see suggestions in error messages to use the 'static lifetime. But
before specifying 'static as the lifetime for a reference, think about
whether or not the reference you have actually lives the entire lifetime of
your program, and whether you want it to. Most of the time, an error message
suggesting the 'static lifetime results from attempting to create a dangling
reference or a mismatch of the available lifetimes. In such cases, the solution
is to fix those problems, not to specify the 'static lifetime.
泛型类型参数、特征约束与生命周期 (Generic Type Parameters, Trait Bounds, and Lifetimes)
Generic Type Parameters, Trait Bounds, and Lifetimes
让我们简要地看一下在一个函数中同时指定泛型类型参数、特征约束和生命周期的语法!
Let’s briefly look at the syntax of specifying generic type parameters, trait bounds, and lifetimes all in one function!
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-11-generics-traits-and-lifetimes/src/main.rs:here}}
}
这是示例 10-21 中的 longest 函数,它返回两个字符串切片中较长的一个。但现在它多了一个名为 ann 的泛型类型 T 参数,它可以由满足 where 子句指定的 Display 特征的任何类型填充。这个额外的参数将使用 {} 打印,这就是为什么需要 Display 特征约束的原因。因为生命周期也是一种泛型,所以生命周期参数 'a 和泛型类型参数 T 的声明放在函数名后尖括号内的同一个列表中。
This is the longest function from Listing 10-21 that returns the longer of
two string slices. But now it has an extra parameter named ann of the generic
type T, which can be filled in by any type that implements the Display
trait as specified by the where clause. This extra parameter will be printed
using {}, which is why the Display trait bound is necessary. Because
lifetimes are a type of generic, the declarations of the lifetime parameter
'a and the generic type parameter T go in the same list inside the angle
brackets after the function name.
总结 (Summary)
Summary
我们在本章中涵盖了很多内容!既然你了解了泛型类型参数、特征与特征约束以及泛型生命周期参数,你就准备好编写没有重复且能在许多不同情况下工作的代码了。泛型类型参数让你能将代码应用于不同的类型。特征与特征约束确保即使类型是泛型的,它们也将具有代码所需的行为。你学习了如何使用生命周期标注来确保这种灵活的代码不会产生任何悬垂引用。而所有这些分析都发生在编译时,不会影响运行时性能!
We covered a lot in this chapter! Now that you know about generic type parameters, traits and trait bounds, and generic lifetime parameters, you’re ready to write code without repetition that works in many different situations. Generic type parameters let you apply the code to different types. Traits and trait bounds ensure that even though the types are generic, they’ll have the behavior the code needs. You learned how to use lifetime annotations to ensure that this flexible code won’t have any dangling references. And all of this analysis happens at compile time, which doesn’t affect runtime performance!
信不信由你,关于我们在本章讨论的主题还有很多需要学习的内容:第 18 章讨论了特征对象,这是使用特征的另一种方式。还有涉及生命周期标注的更复杂场景,你只会在非常高级的场景中需要它们;对于这些,你应该阅读 Rust 参考手册。但接下来,你将学习如何在 Rust 中编写测试,以便确保你的代码正在按其应有的方式工作。
Believe it or not, there is much more to learn on the topics we discussed in this chapter: Chapter 18 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read the Rust Reference. But next, you’ll learn how to write tests in Rust so that you can make sure your code is working the way it should.