Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T14:57:37Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: cc8b098538391d5e5f28e66c126209262e9aaf55d12f99056950be043d748679 source_path: ch19-03-pattern-syntax.md workflow: 16

模式语法 (Pattern Syntax)

在本节中,我们收集了模式中所有有效的语法,并讨论了你可能想要使用每种语法的理由和时机。

In this section, we gather all the syntax that is valid in patterns and discuss why and when you might want to use each one.

匹配字面量 (Matching Literals)

正如你在第 6 章中看到的,你可以直接将模式与字面量进行匹配。以下代码提供了一些示例:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-01-literals/src/main.rs:here}}
}

这段代码打印 one ,因为 x 中的值是 1 。当你希望代码在获得特定具体值时采取行动时,这种语法很有用。

This code prints one because the value in x is 1. This syntax is useful when you want your code to take an action if it gets a particular concrete value.

匹配命名变量 (Matching Named Variables)

命名变量是匹配任何值的不可反驳模式,我们在本书中已经多次使用过它们。然而,当你在 matchif letwhile let 表达式中使用命名变量时,情况会变得复杂。因为这些种类的表达式中的每一个都会开启一个新作用域,所以这些表达式内部作为模式一部分声明的变量将遮蔽结构外部同名的变量,就像所有变量的情况一样。在示例 19-11 中,我们声明了一个名为 x 的变量,其值为 Some(5) ,以及一个名为 y 的变量,其值为 10 。然后我们对值 x 创建一个 match 表达式。在运行此代码或进一步阅读之前,请看 match 分支中的模式和末尾的 println! ,尝试弄清楚代码会打印什么。

Named variables are irrefutable patterns that match any value, and we’ve used them many times in this book. However, there is a complication when you use named variables in match, if let, or while let expressions. Because each of these kinds of expressions starts a new scope, variables declared as part of a pattern inside these expressions will shadow those with the same name outside the constructs, as is the case with all variables. In Listing 19-11, we declare a variable named x with the value Some(5) and a variable y with the value 10. We then create a match expression on the value x. Look at the patterns in the match arms and println! at the end, and try to figure out what the code will print before running this code or reading further.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-11/src/main.rs:here}}
}

让我们逐步了解 match 表达式运行时会发生什么。第一个 match 分支中的模式与 x 的定义值不匹配,因此代码继续运行。

Let’s walk through what happens when the match expression runs. The pattern in the first match arm doesn’t match the defined value of x, so the code continues.

第二个 match 分支中的模式引入了一个名为 y 的新变量,它将匹配 Some 值内部的任何值。因为我们处于 match 表达式内部的一个新作用域中,这是一个新的 y 变量,而不是我们在开头声明的且值为 10 的那个 y 。这个新的 y 绑定将匹配 Some 内部的任何值,这正是我们在 x 中拥有的。因此,这个新的 y 绑定到 xSome 的内部值。那个值是 5 ,所以该分支的表达式执行并打印 Matched, y = 5

The pattern in the second match arm introduces a new variable named y that will match any value inside a Some value. Because we’re in a new scope inside the match expression, this is a new y variable, not the y we declared at the beginning with the value 10. This new y binding will match any value inside a Some, which is what we have in x. Therefore, this new y binds to the inner value of the Some in x. That value is 5, so the expression for that arm executes and prints Matched, y = 5.

如果 x 是一个 None 值而不是 Some(5) ,前两个分支中的模式将不匹配,因此该值将匹配到下划线。我们没有在下划线分支的模式中引入 x 变量,因此表达式中的 x 仍然是未被遮蔽的外部 x 。在这种假设的情况下, match 将打印 Default case, x = None

If x had been a None value instead of Some(5), the patterns in the first two arms wouldn’t have matched, so the value would have matched to the underscore. We didn’t introduce the x variable in the pattern of the underscore arm, so the x in the expression is still the outer x that hasn’t been shadowed. In this hypothetical case, the match would print Default case, x = None.

match 表达式执行完毕后,它的作用域结束,内部 y 的作用域也随之结束。最后一条 println! 产生 at the end: x = Some(5), y = 10

When the match expression is done, its scope ends, and so does the scope of the inner y. The last println! produces at the end: x = Some(5), y = 10.

要创建一个比较外部 xy 值的 match 表达式,而不是引入一个遮蔽现有 y 变量的新变量,我们需要改用 match 守卫 (match guard) 条件。我们将在稍后的“使用 match 守卫添加条件”一节中讨论 match 守卫。

To create a match expression that compares the values of the outer x and y, rather than introducing a new variable that shadows the existing y variable, we would need to use a match guard conditional instead. We’ll talk about match guards later in the “Adding Conditionals with Match Guards” section.

匹配多个模式 (Matching Multiple Patterns)

match 表达式中,你可以使用 | 语法匹配多个模式,这是模式“或 (or)”运算符。例如,在以下代码中,我们将 x 的值与 match 分支进行匹配,其中第一个分支有一个“或”选项,这意味着如果 x 的值匹配该分支中的任一值,该分支的代码就会运行:

In match expressions, you can match multiple patterns using the | syntax, which is the pattern or operator. For example, in the following code, we match the value of x against the match arms, the first of which has an or option, meaning if the value of x matches either of the values in that arm, that arm’s code will run:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-02-multiple-patterns/src/main.rs:here}}
}

这段代码打印 one or two

This code prints one or two.

使用 ..= 匹配值范围 (Matching Ranges of Values with ..=)

..= 语法允许我们匹配一个闭区间范围内的值。在以下代码中,当一个模式匹配给定范围内的任何值时,该分支将执行:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-03-ranges/src/main.rs:here}}
}

如果 x12345 ,第一个分支将匹配。与使用 | 运算符表达相同想法相比,这种语法对于匹配多个值更方便;如果要使用 | ,我们就必须指定 1 | 2 | 3 | 4 | 5 。指定一个范围要短得多,特别是如果我们想匹配,比如 1 到 1,000 之间的任何数字!

If x is 1, 2, 3, 4, or 5, the first arm will match. This syntax is more convenient for multiple match values than using the | operator to express the same idea; if we were to use |, we would have to specify 1 | 2 | 3 | 4 | 5. Specifying a range is much shorter, especially if we want to match, say, any number between 1 and 1,000!

编译器在编译时会检查范围是否为空,并且由于 Rust 只能对 char 和数值判断其范围是否为空,因此范围只允许用于数值或 char 值。

The compiler checks that the range isn’t empty at compile time, and because the only types for which Rust can tell if a range is empty or not are char and numeric values, ranges are only allowed with numeric or char values.

这里有一个使用 char 值范围的例子:

Here is an example using ranges of char values:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-04-ranges-of-char/src/main.rs:here}}
}

Rust 可以判断出 'c' 在第一个模式的范围内,并打印 early ASCII letter

Rust can tell that 'c' is within the first pattern’s range and prints early ASCII letter.

解构以分解值 (Destructuring to Break Apart Values)

Destructuring to Break Apart Values

我们也可以使用模式来解构结构体、枚举和元组,以便使用这些值的不同部分。让我们逐个查看这些值。

We can also use patterns to destructure structs, enums, and tuples to use different parts of these values. Let’s walk through each value.

结构体 (Structs)

示例 19-12 显示了一个具有 xy 两个字段的 Point 结构体,我们可以使用带有 let 语句的模式将其分解。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-12/src/main.rs}}
}

这段代码创建了变量 ab ,它们匹配 p 结构体的 xy 字段的值。这个例子说明模式中变量的名称不必与结构体的字段名匹配。然而,将变量名与字段名匹配是很常见的,以便更容易记住哪些变量来自哪些字段。由于这种常见用法,并且由于编写 let Point { x: x, y: y } = p; 包含大量重复,Rust 提供了一种匹配结构体字段的模式简写:你只需要列出结构体字段的名称,从模式中创建的变量将具有相同的名称。示例 19-13 的行为与示例 19-12 中的代码相同,但在 let 模式中创建的变量是 xy 而不是 ab

This code creates the variables a and b that match the values of the x and y fields of the p struct. This example shows that the names of the variables in the pattern don’t have to match the field names of the struct. However, it’s common to match the variable names to the field names to make it easier to remember which variables came from which fields. Because of this common usage, and because writing let Point { x: x, y: y } = p; contains a lot of duplication, Rust has a shorthand for patterns that match struct fields: You only need to list the name of the struct field, and the variables created from the pattern will have the same names. Listing 19-13 behaves in the same way as the code in Listing 19-12, but the variables created in the let pattern are x and y instead of a and b.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-13/src/main.rs}}
}

这段代码创建了匹配变量 pxy 字段的变量 xy 。其结果是变量 xy 包含了来自 p 结构体的值。

This code creates the variables x and y that match the x and y fields of the p variable. The outcome is that the variables x and y contain the values from the p struct.

我们也可以将字面量值作为结构体模式的一部分进行解构,而不是为所有字段都创建变量。这样做允许我们测试某些字段是否具有特定值,同时创建变量来解构其他字段。

We can also destructure with literal values as part of the struct pattern rather than creating variables for all the fields. Doing so allows us to test some of the fields for particular values while creating variables to destructure the other fields.

在示例 19-14 中,我们有一个 match 表达式,它将 Point 值分为三种情况:直接位于 x 轴上(当 y = 0 时为真)、位于 y 轴上( x = 0 )或不在任一轴上的点。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-14/src/main.rs:here}}
}

通过指定 y 字段如果其值匹配字面量 0 则匹配,第一个分支将匹配位于 x 轴上的任何点。该模式仍然创建了一个 x 变量,我们可以在该分支的代码中使用它。

The first arm will match any point that lies on the x axis by specifying that the y field matches if its value matches the literal 0. The pattern still creates an x variable that we can use in the code for this arm.

类似地,第二个分支通过指定 x 字段如果其值为 0 则匹配来匹配 y 轴上的任何点,并为 y 字段的值创建一个变量 y 。第三个分支没有指定任何字面量,因此它匹配任何其他 Point 并为 xy 字段都创建变量。

Similarly, the second arm matches any point on the y axis by specifying that the x field matches if its value is 0 and creates a variable y for the value of the y field. The third arm doesn’t specify any literals, so it matches any other Point and creates variables for both the x and y fields.

在这个例子中,值 p 由于 x 包含 0 而匹配第二个分支,所以这段代码将打印 On the y axis at 7

In this example, the value p matches the second arm by virtue of x containing a 0, so this code will print On the y axis at 7.

记住 match 表达式一旦找到第一个匹配的模式就会停止检查分支,所以即使 Point { x: 0, y: 0 } 既在 x 轴也在 y 轴上,这段代码也只会打印 On the x axis at 0

Remember that a match expression stops checking arms once it has found the first matching pattern, so even though Point { x: 0, y: 0 } is on the x axis and the y axis, this code would only print On the x axis at 0.

枚举 (Enums)

我们在本书中解构过枚举(例如第 6 章的示例 6-5),但我们还没有明确讨论过解构枚举的模式与枚举中存储数据定义的方式相对应。举个例子,在示例 19-15 中,我们使用示例 6-2 中的 Message 枚举,并编写了一个带有模式的 match ,这些模式将解构每个内部值。

We’ve destructured enums in this book (for example, Listing 6-5 in Chapter 6), but we haven’t yet explicitly discussed that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined. As an example, in Listing 19-15, we use the Message enum from Listing 6-2 and write a match with patterns that will destructure each inner value.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-15/src/main.rs}}
}

这段代码将打印 Change color to red 0, green 160, and blue 255 。尝试更改 msg 的值,看看其他分支的代码运行情况。

This code will print Change color to red 0, green 160, and blue 255. Try changing the value of msg to see the code from the other arms run.

对于没有任何数据的枚举变体,如 Message::Quit ,我们无法进一步解构该值。我们只能匹配字面量 Message::Quit 值,且该模式中没有变量。

For enum variants without any data, like Message::Quit, we can’t destructure the value any further. We can only match on the literal Message::Quit value, and no variables are in that pattern.

对于类结构体枚举变体,如 Message::Move ,我们可以使用类似于匹配结构体所指定的模式。在变体名称之后,我们放置花括号,然后列出带有变量的字段,以便分解出各部分在该分支的代码中使用。在这里,我们使用了示例 19-13 中所用的简写形式。

For struct-like enum variants, such as Message::Move, we can use a pattern similar to the pattern we specify to match structs. After the variant name, we place curly brackets and then list the fields with variables so that we break apart the pieces to use in the code for this arm. Here we use the shorthand form as we did in Listing 19-13.

对于类元组枚举变体,如持有一个包含一个元素元组的 Message::Write 和持有一个包含三个元素元组的 Message::ChangeColor ,其模式类似于匹配元组所指定的模式。模式中变量的数量必须与我们要匹配的变体中元素的数量相匹配。

For tuple-like enum variants, like Message::Write that holds a tuple with one element and Message::ChangeColor that holds a tuple with three elements, the pattern is similar to the pattern we specify to match tuples. The number of variables in the pattern must match the number of elements in the variant we’re matching.

嵌套的结构体和枚举 (Nested Structs and Enums)

到目前为止,我们的示例都是匹配一层深度的结构体或枚举,但匹配也可以在嵌套项上进行!例如,我们可以重构示例 19-15 中的代码,以在 ChangeColor 消息中支持 RGB 和 HSV 颜色,如示例 19-16 所示。

So far, our examples have all been matching structs or enums one level deep, but matching can work on nested items too! For example, we can refactor the code in Listing 19-15 to support RGB and HSV colors in the ChangeColor message, as shown in Listing 19-16.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-16/src/main.rs}}
}

match 表达式第一个分支的模式匹配一个包含 Color::Rgb 变体的 Message::ChangeColor 枚举变体;然后,模式绑定到三个内部的 i32 值。第二个分支的模式也匹配一个 Message::ChangeColor 枚举变体,但内部枚举匹配的是 Color::Hsv 。我们可以在一个 match 表达式中指定这些复杂的条件,即使涉及到两个枚举。

The pattern of the first arm in the match expression matches a Message::ChangeColor enum variant that contains a Color::Rgb variant; then, the pattern binds to the three inner i32 values. The pattern of the second arm also matches a Message::ChangeColor enum variant, but the inner enum matches Color::Hsv instead. We can specify these complex conditions in one match expression, even though two enums are involved.

结构体和元组 (Structs and Tuples)

我们可以以更复杂的方式混合、匹配和嵌套解构模式。以下示例展示了一个复杂的解构,其中我们在元组内部嵌套了结构体和元组,并解构出所有的原始值:

We can mix, match, and nest destructuring patterns in even more complex ways. The following example shows a complicated destructure where we nest structs and tuples inside a tuple and destructure all the primitive values out:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-05-destructuring-structs-and-tuples/src/main.rs:here}}
}

这段代码让我们能够将复杂类型拆分为其组成部分,以便我们可以分别使用我们感兴趣的值。

This code lets us break complex types into their component parts so that we can use the values we’re interested in separately.

使用模式进行解构是分别使用值各部分(例如结构体中每个字段的值)的一种便捷方式。

Destructuring with patterns is a convenient way to use pieces of values, such as the value from each field in a struct, separately from each other.

忽略模式中的值 (Ignoring Values in a Pattern)

你已经看到,有时忽略模式中的值很有用,例如在 match 的最后一个分支中获取一个通配模式,它实际上不执行任何操作,但确实考虑了所有剩余的可能值。有几种方法可以在模式中忽略整个值或值的部分:使用 _ 模式(你已经见过了)、在另一个模式内部使用 _ 模式、使用以下划线开头的名称,或者使用 .. 来忽略值的剩余部分。让我们探索如何以及为什么要使用这些模式中的每一个。

You’ve seen that it’s sometimes useful to ignore values in a pattern, such as in the last arm of a match, to get a catch-all that doesn’t actually do anything but does account for all remaining possible values. There are a few ways to ignore entire values or parts of values in a pattern: using the _ pattern (which you’ve seen), using the _ pattern within another pattern, using a name that starts with an underscore, or using .. to ignore remaining parts of a value. Let’s explore how and why to use each of these patterns.

使用 _ 忽略整个值 (An Entire Value with _)

我们将下划线用作通配符模式,它将匹配任何值但不绑定到该值。这在 match 表达式的最后一个分支中特别有用,但我们也可以在任何模式中使用它,包括函数参数,如示例 19-17 所示。

We’ve used the underscore as a wildcard pattern that will match any value but not bind to the value. This is especially useful as the last arm in a match expression, but we can also use it in any pattern, including function parameters, as shown in Listing 19-17.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-17/src/main.rs}}
}

这段代码将完全忽略作为第一个参数传入的值 3 ,并打印 This code only uses the y parameter: 4

This code will completely ignore the value 3 passed as the first argument, and will print This code only uses the y parameter: 4.

在大多数情况下,当你不再需要某个特定函数参数时,你会更改签名使其不包含该未使用的参数。忽略函数参数在某些情况下特别有用,例如,当你实现一个特征时,你需要某种特定的类型签名,但实现中的函数体并不需要其中一个参数。这时你就可以避免像使用名称那样得到关于未使用函数参数的编译器警告。

In most cases when you no longer need a particular function parameter, you would change the signature so that it doesn’t include the unused parameter. Ignoring a function parameter can be especially useful in cases when, for example, you’re implementing a trait when you need a certain type signature but the function body in your implementation doesn’t need one of the parameters. You then avoid getting a compiler warning about unused function parameters, as you would if you used a name instead.

使用嵌套的 _ 忽略值的部分 (Parts of a Value with a Nested _)

我们也可以在另一个模式内部使用 _ 来忽略值的一部分,例如,当我们只想测试值的一部分,但在要运行的相应代码中不需要其他部分时。示例 19-18 显示了负责管理设置值的代码。业务要求是,不允许用户覆盖现有的设置自定义,但如果当前未设置,则可以取消设置并为其赋予一个值。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-18/src/main.rs:here}}
}

这段代码将打印 Can't overwrite an existing customized value 接着打印 setting is Some(5) 。在第一个 match 分支中,我们不需要匹配或使用任一 Some 变体内部的值,但我们确实需要测试 setting_valuenew_setting_value 都是 Some 变体的情况。在那种情况下,我们打印不更改 setting_value 的原因,并且它不会被更改。

This code will print Can't overwrite an existing customized value and then setting is Some(5). In the first match arm, we don’t need to match on or use the values inside either Some variant, but we do need to test for the case when setting_value and new_setting_value are the Some variant. In that case, we print the reason for not changing setting_value, and it doesn’t get changed.

在由第二个分支中的 _ 模式表示的所有其他情况(如果 setting_valuenew_setting_value 之一是 None )中,我们希望允许 new_setting_value 成为 setting_value

In all other cases (if either setting_value or new_setting_value is None) expressed by the _ pattern in the second arm, we want to allow new_setting_value to become setting_value.

我们也可以在一个模式的多个地方使用下划线来忽略特定的值。示例 19-19 展示了一个忽略包含五个项的元组中第二个和第四个值的例子。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-19/src/main.rs:here}}
}

这段代码将打印 Some numbers: 2, 8, 32 ,值 416 将被忽略。

This code will print Some numbers: 2, 8, 32, and the values 4 and 16 will be ignored.

通过以 _ 开头命名来忽略未使用的变量 (An Unused Variable by Starting Its Name with _)

如果你创建了一个变量但在任何地方都不使用它,Rust 通常会发出警告,因为未使用的变量可能是一个 bug。然而,有时创建一个你尚未使用的变量是很有用的,比如当你正在开发原型或刚开始一个项目时。在这种情况下,你可以通过以连接号开头命名变量来告诉 Rust 不要就未使用的变量向你发出警告。在示例 19-20 中,我们创建了两个未使用的变量,但当我们编译这段代码时,我们应该只得到关于其中一个的警告。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-20/src/main.rs}}
}

在这里,我们得到了关于不使用变量 y 的警告,但没有得到关于不使用 _x 的警告。

Here, we get a warning about not using the variable y, but we don’t get a warning about not using _x.

注意,仅使用 _ 与使用以下划线开头的名称之间有细微的区别。语法 _x 仍然将值绑定到变量,而 _ 则完全不绑定。为了展示这种区别的重要性,示例 19-21 将为我们提供一个错误。

Note that there is a subtle difference between using only _ and using a name that starts with an underscore. The syntax _x still binds the value to the variable, whereas _ doesn’t bind at all. To show a case where this distinction matters, Listing 19-21 will provide us with an error.

{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-21/src/main.rs:here}}

我们将收到一个错误,因为 s 值仍将被移动到 _s 中,这阻止了我们再次使用 s 。然而,单独使用下划线永远不会绑定到该值。示例 19-22 将可以编译且没有任何错误,因为 s 不会被移动到 _ 中。

We’ll receive an error because the s value will still be moved into _s, which prevents us from using s again. However, using the underscore by itself doesn’t ever bind to the value. Listing 19-22 will compile without any errors because s doesn’t get moved into _.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-22/src/main.rs:here}}
}

这段代码运行良好,因为我们从未将 s 绑定到任何东西上;它没有被移动。

This code works just fine because we never bind s to anything; it isn’t moved.

使用 .. 忽略值的剩余部分 (Remaining Parts of a Value with ..)

对于具有许多部分的值,我们可以使用 .. 语法来使用特定的部分并忽略其余部分,从而避免为每个被忽略的值列出下划线。 .. 模式忽略了我们在模式的其余部分中没有显式匹配的值的任何部分。在示例 19-23 中,我们有一个 Point 结构体,它持有三维空间中的一个坐标。在 match 表达式中,我们只想对 x 坐标进行操作,并忽略 yz 字段中的值。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-23/src/main.rs:here}}
}

我们列出 x 值,然后直接包含 .. 模式。这比必须列出 y: _z: _ 要快,特别是在只有一两个字段相关而结构体具有许多字段的情况下工作时。

We list the x value and then just include the .. pattern. This is quicker than having to list y: _ and z: _, particularly when we’re working with structs that have lots of fields in situations where only one or two fields are relevant.

语法 .. 会根据需要扩展为尽可能多的值。示例 19-24 显示了如何对元组使用 ..

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-24/src/main.rs}}
}

在这段代码中,第一个和最后一个值分别与 firstlast 匹配。 .. 将匹配并忽略中间的所有内容。

In this code, the first and last values are matched with first and last. The .. will match and ignore everything in the middle.

然而,使用 .. 必须是无歧义的。如果还不清楚哪些值是打算用于匹配的,哪些应该是被忽略的,Rust 会给我们一个错误。示例 19-25 显示了一个以歧义方式使用 .. 的例子,因此它将无法编译。

{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-25/src/main.rs}}

当我们编译这个例子时,我们得到了这个错误:

{{#include ../listings/ch19-patterns-and-matching/listing-19-25/output.txt}}

Rust 不可能确定在将一个值与 second 匹配之前应该忽略元组中的多少个值,以及此后应该再忽略多少个值。这段代码可能意味着我们要忽略 2 ,将 second 绑定到 4 ,然后忽略 81632 ;或者我们要忽略 24 ,将 second 绑定到 8 ,然后忽略 1632 ;等等。变量名 second 对 Rust 来说没有任何特殊含义,所以因为像这样在两个地方使用 .. 具有歧义,我们得到了一个编译器错误。

It’s impossible for Rust to determine how many values in the tuple to ignore before matching a value with second and then how many further values to ignore thereafter. This code could mean that we want to ignore 2, bind second to 4, and then ignore 8, 16, and 32; or that we want to ignore 2 and 4, bind second to 8, and then ignore 16 and 32; and so forth. The variable name second doesn’t mean anything special to Rust, so we get a compiler error because using .. in two places like this is ambiguous.

使用 match 守卫添加条件 (Adding Conditionals with Match Guards)

“match 守卫 (match guard)” 是在 match 分支模式之后指定的额外 if 条件,它也必须匹配才能选择该分支。match 守卫对于表达比单独模式所允许的更复杂的想法很有用。但请注意,它们仅在 match 表达式中可用,而不在 if letwhile let 表达式中。

A match guard is an additional if condition, specified after the pattern in a match arm, that must also match for that arm to be chosen. Match guards are useful for expressing more complex ideas than a pattern alone allows. Note, however, that they are only available in match expressions, not if let or while let expressions.

该条件可以使用在模式中创建的变量。示例 19-26 显示了一个 match ,其中第一个分支具有模式 Some(x) ,并且还具有 if x % 2 == 0 的 match 守卫(如果数字是偶数,则为 true )。

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-26/src/main.rs:here}}
}

本例将打印 The number 4 is even 。当 num 与第一个分支中的模式比较时,它是匹配的,因为 Some(4) 匹配 Some(x) 。然后,match 守卫检查 x 除以 2 的余数是否等于 0,因为是,所以选择了第一个分支。

This example will print The number 4 is even. When num is compared to the pattern in the first arm, it matches because Some(4) matches Some(x). Then, the match guard checks whether the remainder of dividing x by 2 is equal to 0, and because it is, the first arm is selected.

如果 numSome(5) ,第一个分支中的 match 守卫将为 false ,因为 5 除以 2 的余数是 1,不等于 0。Rust 然后会转到第二个分支,它是匹配的,因为第二个分支没有 match 守卫,因此匹配任何 Some 变体。

If num had been Some(5) instead, the match guard in the first arm would have been false because the remainder of 5 divided by 2 is 1, which is not equal to 0. Rust would then go to the second arm, which would match because the second arm doesn’t have a match guard and therefore matches any Some variant.

在模式中无法表达 if x % 2 == 0 条件,因此 match 守卫赋予了我们表达此逻辑的能力。这种额外表达能力的代价是,当涉及到 match 守卫表达式时,编译器不会尝试检查穷尽性。

There is no way to express the if x % 2 == 0 condition within a pattern, so the match guard gives us the ability to express this logic. The downside of this additional expressiveness is that the compiler doesn’t try to check for exhaustiveness when match guard expressions are involved.

在讨论示例 19-11 时,我们提到可以使用 match 守卫来解决我们的模式遮蔽问题。回想一下,我们在 match 表达式内部的模式中创建了一个新变量,而不是使用 match 外部的变量。那个新变量意味着我们无法针对外部变量的值进行测试。示例 19-27 展示了我们如何使用 match 守卫来解决这个问题。

When discussing Listing 19-11, we mentioned that we could use match guards to solve our pattern-shadowing problem. Recall that we created a new variable inside the pattern in the match expression instead of using the variable outside the match. That new variable meant we couldn’t test against the value of the outer variable. Listing 19-27 shows how we can use a match guard to fix this problem.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-27/src/main.rs}}
}

这段代码现在将打印 Default case, x = Some(5) 。第二个 match 分支中的模式没有引入会遮蔽外部 y 的新变量 y ,这意味着我们可以在 match 守卫中使用外部 y 。我们没有将模式指定为 Some(y) (那会遮蔽外部 y ),而是指定了 Some(n) 。这创建了一个新变量 n ,它不会遮蔽任何东西,因为 match 之外没有 n 变量。

This code will now print Default case, x = Some(5). The pattern in the second match arm doesn’t introduce a new variable y that would shadow the outer y, meaning we can use the outer y in the match guard. Instead of specifying the pattern as Some(y), which would have shadowed the outer y, we specify Some(n). This creates a new variable n that doesn’t shadow anything because there is no n variable outside the match.

match 守卫 if n == y 不是一个模式,因此不会引入新变量。这个 y “是”外部的 y 而不是遮蔽它的新 y ,我们可以通过将 ny 进行比较来寻找具有与外部 y 相同值的值。

The match guard if n == y is not a pattern and therefore doesn’t introduce new variables. This y is the outer y rather than a new y shadowing it, and we can look for a value that has the same value as the outer y by comparing n to y.

你也可以在 match 守卫中使用“或”运算符 | 来指定多个模式;match 守卫条件将应用于所有模式。示例 19-28 显示了将使用 | 的模式与 match 守卫结合时的优先级。本例的重要部分是 if y match 守卫应用于 45 “以及” 6 ,即使它看起来可能像 if y 只应用于 6

You can also use the or operator | in a match guard to specify multiple patterns; the match guard condition will apply to all the patterns. Listing 19-28 shows the precedence when combining a pattern that uses | with a match guard. The important part of this example is that the if y match guard applies to 4, 5, and 6, even though it might look like if y only applies to 6.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-28/src/main.rs:here}}
}

match 条件声明该分支只有在 x 的值等于 456 “并且” ytrue 时才匹配。当这段代码运行时,第一个分支的模式匹配是因为 x4 ,但 match 守卫 if yfalse ,因此第一个分支没有被选中。代码转到第二个分支,它是匹配的,于是这个程序打印 no 。原因是 if 条件应用于整个模式 4 | 5 | 6 ,而不仅仅是最后一个值 6 。换句话说,match 守卫相对于模式的优先级表现如下:

The match condition states that the arm only matches if the value of x is equal to 4, 5, or 6 and if y is true. When this code runs, the pattern of the first arm matches because x is 4, but the match guard if y is false, so the first arm is not chosen. The code moves on to the second arm, which does match, and this program prints no. The reason is that the if condition applies to the whole pattern 4 | 5 | 6, not just to the last value 6. In other words, the precedence of a match guard in relation to a pattern behaves like this:

(4 | 5 | 6) if y => ...

而不是这样:

rather than this:

4 | 5 | (6 if y) => ...

运行代码后,优先级行为显而易见:如果 match 守卫仅应用于使用 | 运算符指定的列表中的最后一个值,则该分支本应匹配,并且程序会打印出 yes

After running the code, the precedence behavior is evident: If the match guard were applied only to the final value in the list of values specified using the | operator, the arm would have matched, and the program would have printed yes.

使用 @ 绑定 (Using @ Bindings)

at 运算符 @ 允许我们在测试一个值是否匹配模式的同时创建一个持有该值的变量。在示例 19-29 中,我们要测试 Message::Helloid 字段是否在范围 3..=7 内。我们还想将该值绑定到变量 id ,以便我们可以在与该分支关联的代码中使用它。

The at operator @ lets us create a variable that holds a value at the same time we’re testing that value for a pattern match. In Listing 19-29, we want to test that a Message::Hello id field is within the range 3..=7. We also want to bind the value to the variable id so that we can use it in the code associated with the arm.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-29/src/main.rs:here}}
}

本例将打印 Found an id in range: 5 。通过在范围 3..=7 之前指定 id @ ,我们捕捉到了任何匹配该范围的值并放入一个名为 id 的变量中,同时也测试了该值是否匹配该范围模式。

This example will print Found an id in range: 5. By specifying id @ before the range 3..=7, we’re capturing whatever value matched the range in a variable named id while also testing that the value matched the range pattern.

在第二个分支中,模式中仅指定了一个范围,与该分支关联的代码就没有包含 id 字段实际值的变量。 id 字段的值可能是 10、11 或 12,但与该模式配合的代码并不知道它是哪个。由于我们没有将 id 的值保存在变量中,模式代码无法使用来自 id 字段的值。

In the second arm, where we only have a range specified in the pattern, the code associated with the arm doesn’t have a variable that contains the actual value of the id field. The id field’s value could have been 10, 11, or 12, but the code that goes with that pattern doesn’t know which it is. The pattern code isn’t able to use the value from the id field because we haven’t saved the id value in a variable.

在最后一个分支中,我们指定了一个没有范围的变量,在分支代码中确实可以使用名为 id 的变量中可用的值。原因是由于我们使用了结构体字段简写语法。但在这个分支中,我们没有像前两个分支那样对 id 字段中的值应用任何测试:任何值都会匹配这个模式。

In the last arm, where we’ve specified a variable without a range, we do have the value available to use in the arm’s code in a variable named id. The reason is that we’ve used the struct field shorthand syntax. But we haven’t applied any test to the value in the id field in this arm, as we did with the first two arms: Any value would match this pattern.

使用 @ 让我们能在一个模式内测试一个值并将其保存在变量中。

Using @ lets us test a value and save it in a variable within one pattern.

总结 (Summary)

Rust 的模式在区分不同种类的数据时非常有用。当在 match 表达式中使用时,Rust 确保你的模式覆盖了每一个可能的值,否则你的程序将无法编译。 let 语句和函数参数中的模式使这些结构更有用,能够将值解构为更小的部分并将这些部分分配给变量。我们可以创建简单或复杂的模式来满足我们的需求。

Rust’s patterns are very useful in distinguishing between different kinds of data. When used in match expressions, Rust ensures that your patterns cover every possible value, or your program won’t compile. Patterns in let statements and function parameters make those constructs more useful, enabling the destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs.

接下来,作为本书的倒数第二章,我们将看看 Rust 各种特性的一些高级方面。走吧!

Next, for the penultimate chapter of the book, we’ll look at some advanced aspects of a variety of Rust’s features. Let’s go!