模式语法
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 章中所看到的,你可以直接将模式与字面量进行匹配。以下代码给出了一些示例:
As you saw in Chapter 6, you can match patterns against literals directly. The following code gives some examples:
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
这段代码打印 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
命名变量是匹配任何值的不可反驳模式,我们在本书中已经多次使用过它们。但是,当你在 match、if let 或 while let 表达式中使用命名变量时,情况会变得复杂。因为这些表达式中的每一个都会开启一个新的作用域,所以在这些表达式内部作为模式一部分声明的变量将遮蔽(shadow)这些构造之外的同名变量,就像所有变量的情况一样。在示例 19-11 中,我们声明了一个名为 x 的变量,其值为 Some(5),以及一个值为 10 的变量 y。然后,我们在值 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.
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
让我们逐步了解 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.
第二个匹配分支中的模式引入了一个名为 y 的新变量,它将匹配 Some 值中的任何值。因为我们在 match 表达式内部的一个新作用域中,所以这是一个新的 y 变量,而不是我们在开头声明的值为 10 的 y。这个新的 y 绑定将匹配 Some 内部的任何值,这正是我们在 x 中所拥有的。因此,这个新的 y 绑定到 x 中 Some 的内部值。那个值是 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.
为了创建一个比较外部 x 和 y 值的 match 表达式,而不是引入一个遮蔽现有 y 变量的新变量,我们需要使用匹配守卫(match guard)条件。我们将在稍后的“通过匹配守卫添加额外条件”部分讨论匹配守卫。
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 的值与匹配分支进行匹配,其中第一个分支有一个“或”选项,这意味着如果 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:
fn main() {
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
这段代码打印 one or two。
This code prints one or two.
使用 ..= 匹配值范围
Matching Ranges of Values with ..=
..= 语法允许我们匹配一个包含端点的值范围。在下面的代码中,当一个模式匹配给定范围内的任何值时,该分支将执行:
The ..= syntax allows us to match to an inclusive range of values. In the
following code, when a pattern matches any of the values within the given
range, that arm will execute:
fn main() {
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
}
如果 x 是 1、2、3、4 或 5,第一个分支将匹配。对于多个匹配值,这种语法比使用 | 运算符表达相同意思更方便;如果我们要使用 |,则必须指定 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:
fn main() {
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
}
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
我们还可以使用模式来解构结构体、枚举和元组,以便使用这些值的不同部分。让我们逐个看看。
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 显示了一个具有两个字段 x 和 y 的 Point 结构体,我们可以使用带有 let 语句的模式将其分解。
Listing 19-12 shows a Point struct with two fields, x and y, that we can
break apart using a pattern with a let statement.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
这段代码创建了变量 a 和 b,它们匹配 p 结构体的 x 和 y 字段的值。这个例子表明模式中的变量名不一定要与结构体的字段名匹配。但是,通常会将变量名与字段名相匹配,以便更容易记住哪些变量来自哪些字段。由于这种常见用法,并且由于编写 let Point { x: x, y: y } = p; 包含很多重复,Rust 为匹配结构体字段的模式提供了一种简写:你只需要列出结构体字段的名称,从模式创建的变量将具有相同的名称。示例 19-13 的行为与示例 19-12 中的代码相同,但在 let 模式中创建的变量是 x 和 y 而不是 a 和 b。
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.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
这段代码创建了匹配 p 变量的 x 和 y 字段的变量 x 和 y。结果是变量 x 和 y 包含了 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),或者不位于任何轴上的点。
In Listing 19-14, we have a match expression that separates Point values
into three cases: points that lie directly on the x axis (which is true when
y = 0), on the y axis (x = 0), or on neither axis.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
第一个分支通过指定 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 并为 x 和 y 字段都创建变量。
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.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
}
}
这段代码将打印 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.
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}");
}
_ => (),
}
}
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:
fn main() {
struct Point {
x: i32,
y: i32,
}
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}
这段代码让我们能够将复杂的类型分解为其组成部分,以便我们可以分别使用我们感兴趣的值。
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.
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
这段代码将完全忽略作为第一个参数传递的值 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.
在大多数情况下,当你不再需要某个特定的函数参数时,你会更改签名以使其不包含未使用的参数。忽略函数参数在某些情况下特别有用,例如当你正在实现一个 trait 时,你需要特定的类型签名,但你的实现中的函数体不需要其中的一个参数。这样你就可以避免像使用名称时那样收到关于未使用函数参数的编译器警告。
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 展示了负责管理设置值的代码。业务要求是:不允许用户覆盖设置的现有自定义值,但如果当前未设置,则可以取消设置并赋予其一个值。
We can also use _ inside another pattern to ignore just part of a value, for
example, when we want to test for only part of a value but have no use for the
other parts in the corresponding code we want to run. Listing 19-18 shows code
responsible for managing a setting’s value. The business requirements are that
the user should not be allowed to overwrite an existing customization of a
setting but can unset the setting and give it a value if it is currently unset.
fn main() {
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {setting_value:?}");
}
这段代码将打印 Can't overwrite an existing customized value,然后打印 setting is Some(5)。在第一个匹配分支中,我们不需要匹配或使用任何一个 Some 变体内部的值,但我们确实需要测试 setting_value 和 new_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_value 或 new_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 展示了一个忽略包含五个项的元组中第二个和第四个值的例子。
We can also use underscores in multiple places within one pattern to ignore particular values. Listing 19-19 shows an example of ignoring the second and fourth values in a tuple of five items.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}
}
这段代码将打印 Some numbers: 2, 8, 32,而值 4 和 16 将被忽略。
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 中,我们创建了两个未使用的变量,但当我们编译此代码时,我们应该只收到关于其中一个变量的警告。
If you create a variable but don’t use it anywhere, Rust will usually issue a warning because an unused variable could be a bug. However, sometimes it’s useful to be able to create a variable you won’t use yet, such as when you’re prototyping or just starting a project. In this situation, you can tell Rust not to warn you about the unused variable by starting the name of the variable with an underscore. In Listing 19-20, we create two unused variables, but when we compile this code, we should only get a warning about one of them.
fn main() {
let _x = 5;
let y = 10;
}
在这里,我们收到了关于不使用变量 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.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
我们将收到一个错误,因为 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 _.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");
}
这段代码运行得很好,因为我们从未将 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 坐标进行操作,并忽略 y 和 z 字段中的值。
With values that have many parts, we can use the .. syntax to use specific
parts and ignore the rest, avoiding the need to list underscores for each
ignored value. The .. pattern ignores any parts of a value that we haven’t
explicitly matched in the rest of the pattern. In Listing 19-23, we have a
Point struct that holds a coordinate in three-dimensional space. In the
match expression, we want to operate only on the x coordinate and ignore
the values in the y and z fields.
fn main() {
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"),
}
}
我们列出 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 展示了如何对元组使用 ..。
The syntax .. will expand to as many values as it needs to be. Listing 19-24
shows how to use .. with a tuple.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
在这段代码中,第一个和最后一个值与 first 和 last 匹配。.. 将匹配并忽略中间的所有内容。
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 展示了一个歧义地使用 .. 的例子,因此它将无法编译。
However, using .. must be unambiguous. If it is unclear which values are
intended for matching and which should be ignored, Rust will give us an error.
Listing 19-25 shows an example of using .. ambiguously, so it will not
compile.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
当我们编译这个例子时,会得到如下错误:
When we compile this example, we get this error:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
error: could not compile `patterns` (bin "patterns") due to 1 previous error
对于 Rust 来说,不可能确定在将值与 second 匹配之前要忽略元组中的多少个值,以及在那之后又要忽略多少个值。这段代码可能意味着我们要忽略 2,将 second 绑定到 4,然后忽略 8、16 和 32;或者我们要忽略 2 和 4,将 second 绑定到 8,然后忽略 16 和 32;等等。变量名 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.
通过匹配守卫添加额外条件
Adding Conditionals with Match Guards
匹配守卫 (match guard) 是在 match 分支模式之后指定的额外 if 条件,该条件也必须匹配才能选择该分支。匹配守卫对于表达比单独模式所允许的更复杂的想法非常有用。但是请注意,它们仅在 match 表达式中可用,而不能在 if let 或 while 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 的匹配守卫(如果数字为偶数,则为 true)。
The condition can use variables created in the pattern. Listing 19-26 shows a
match where the first arm has the pattern Some(x) and also has a match
guard of if x % 2 == 0 (which will be true if the number is even).
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
Some(x) => println!("The number {x} is odd"),
None => (),
}
}
这个例子将打印 The number 4 is even。当 num 与第一个分支中的模式比较时,它会匹配,因为 Some(4) 匹配 Some(x)。然后,匹配守卫检查 x 除以 2 的余数是否等于 0,既然等于 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.
如果 num 是 Some(5),第一个分支中的匹配守卫将为 false,因为 5 除以 2 的余数是 1,不等于 0。Rust 然后会转到第二个分支,由于第二个分支没有匹配守卫,因此匹配任何 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 条件,因此匹配守卫赋予了我们表达这种逻辑的能力。这种额外表达能力的缺点是,当涉及匹配守卫表达式时,编译器不会尝试检查穷尽性。
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 之外的变量。那个新变量意味着我们无法针对外部变量的值进行测试。示例 19-27 展示了我们如何使用匹配守卫来解决这个问题。
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.
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
这段代码现在将打印 Default case, x = Some(5)。第二个匹配分支中的模式没有引入一个会遮蔽外部 y 的新变量 y,这意味着我们可以在匹配守卫中使用外部 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.
匹配守卫 if n == y 不是模式,因此不会引入新变量。这里的 y 是外部的 y,而不是遮蔽它的新 y,我们可以通过将 n 与 y 进行比较来寻找具有与外部 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.
你也可以在匹配守卫中使用“或”运算符 | 来指定多种模式;匹配守卫条件将应用于所有模式。示例 19-28 展示了将使用 | 的模式与匹配守卫结合时的优先级。此示例的重要部分是 if y 匹配守卫应用于 4、5 和 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.
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
匹配条件指出,仅当 x 的值等于 4、5 或 6 且 y 为 true 时,该分支才匹配。运行此代码时,第一个分支的模式匹配,因为 x 为 4,但匹配守卫 if y 为 false,因此未选择第一个分支。代码移至第二个分支,该分支匹配,此程序打印 no。原因是 if 条件应用于整个模式 4 | 5 | 6,而不仅仅是最后一个值 6。换句话说,匹配守卫相对于模式的优先级表现如下:
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) => ...
运行代码后,优先级行为是显而易见的:如果匹配守卫仅应用于使用 | 运算符指定的值列表中的最后一个值,则该分支将匹配,程序将打印 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::Hello 的 id 字段是否在范围 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.
fn main() {
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id @ 3..=7 } => {
println!("Found an id in range: {id}")
}
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {id}"),
}
}
这个例子将打印 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.