x-i18n: generated_at: “2026-03-01T13:49:04Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: db9a32dfa0c24d89d74362e0bbdb9e5793a6e61fe3aa487b67897b1cb3f90899 source_path: ch06-02-match.md workflow: 16
match 控制流结构 (match Control Flow Construct)
match Control Flow Construct
Rust 拥有一个极其强大的控制流结构,称为 match,它允许你将一个值与一系列模式进行比较,然后根据匹配的模式执行代码。模式可以由字面量值、变量名、通配符和许多其他内容组成;第 19 章涵盖了所有不同种类的模式及其作用。match 的力量来自于模式的表达能力以及编译器确认所有可能情况都已得到处理这一事实。
Rust has an extremely powerful control flow construct called match that
allows you to compare a value against a series of patterns and then execute
code based on which pattern matches. Patterns can be made up of literal values,
variable names, wildcards, and many other things; Chapter
19 covers all the different kinds of patterns
and what they do. The power of match comes from the expressiveness of the
patterns and the fact that the compiler confirms that all possible cases are
handled.
可以把 match 表达式想象成一台硬币分拣机:硬币沿着一条轨道滑行,轨道上分布着大小不一的孔洞,每枚硬币都会从它遇到的第一个能钻进去的孔洞掉下去。以同样的方式,值会经过 match 中的每个模式,在值“适合”的第一个模式处,该值就会掉入关联的代码块中,以便在执行期间使用。
Think of a match expression as being like a coin-sorting machine: Coins slide
down a track with variously sized holes along it, and each coin falls through
the first hole it encounters that it fits into. In the same way, values go
through each pattern in a match, and at the first pattern the value “fits,”
the value falls into the associated code block to be used during execution.
说到硬币,让我们用它们作为使用 match 的例子!我们可以编写一个函数,它接收一枚未知的美国硬币,并以类似于分拣机的方式确定它是哪种硬币,并返回其以美分为单位的价值,如示例 6-3 所示。
Speaking of coins, let’s use them as an example using match! We can write a
function that takes an unknown US coin and, in a similar way as the counting
machine, determines which coin it is and returns its value in cents, as shown
in Listing 6-3.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-03/src/main.rs:here}}
}
让我们分解 value_in_cents 函数中的 match。首先,我们列出 match 关键字,后跟一个表达式,在这个例子中是值 coin。这看起来与 if 使用的条件表达式非常相似,但有一个很大的区别:对于 if,条件需要求值为布尔值,但在这里它可以是任何类型。在这个例子中,coin 的类型是我们第一行定义的 Coin 枚举。
Let’s break down the match in the value_in_cents function. First, we list
the match keyword followed by an expression, which in this case is the value
coin. This seems very similar to a conditional expression used with if, but
there’s a big difference: With if, the condition needs to evaluate to a
Boolean value, but here it can be any type. The type of coin in this example
is the Coin enum that we defined on the first line.
接下来是 match 分支 (arms)。一个分支有两个部分:一个模式 (pattern) 和一些代码。这里的第一个分支有一个模式,即值 Coin::Penny,然后是 => 运算符,它分隔了模式和要运行的代码。在这个例子中,代码只是值 1。每个分支都用逗号与下一个分支隔开。
Next are the match arms. An arm has two parts: a pattern and some code. The
first arm here has a pattern that is the value Coin::Penny and then the =>
operator that separates the pattern and the code to run. The code in this case
is just the value 1. Each arm is separated from the next with a comma.
当 match 表达式执行时,它会按顺序将结果值与每个分支的模式进行比较。如果模式与值匹配,则执行与该模式关联的代码。如果该模式与值不匹配,则继续执行下一个分支,就像在硬币分拣机中一样。我们可以根据需要拥有任意数量的分支:在示例 6-3 中,我们的 match 有四个分支。
When the match expression executes, it compares the resultant value against
the pattern of each arm, in order. If a pattern matches the value, the code
associated with that pattern is executed. If that pattern doesn’t match the
value, execution continues to the next arm, much as in a coin-sorting machine.
We can have as many arms as we need: In Listing 6-3, our match has four arms.
与每个分支关联的代码是一个表达式,匹配分支中表达式的结果值就是整个 match 表达式返回的值。
The code associated with each arm is an expression, and the resultant value of
the expression in the matching arm is the value that gets returned for the
entire match expression.
如果 match 分支代码很短(如示例 6-3 中每个分支仅返回一个值),我们通常不使用花括号。如果你想在 match 分支中运行多行代码,必须使用花括号,此时分支后的逗号是可选的。例如,以下代码在每次使用 Coin::Penny 调用方法时都会打印 “Lucky penny!”,但它仍然返回代码块的最后一个值 1:
We don’t typically use curly brackets if the match arm code is short, as it is
in Listing 6-3 where each arm just returns a value. If you want to run multiple
lines of code in a match arm, you must use curly brackets, and the comma
following the arm is then optional. For example, the following code prints
“Lucky penny!” every time the method is called with a Coin::Penny, but it
still returns the last value of the block, 1:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-08-match-arm-multiple-lines/src/main.rs:here}}
}
绑定到值的模式 (Patterns That Bind to Values)
Patterns That Bind to Values
match 分支的另一个有用功能是它们可以绑定到匹配模式的值的部分。这就是我们如何从枚举变体中提取值的方法。
Another useful feature of match arms is that they can bind to the parts of the values that match the pattern. This is how we can extract values out of enum variants.
举个例子,让我们更改其中一个枚举变体以在内部持有数据。从 1999 年到 2008 年,美国铸造了 25 美分硬币(quarters),其一面为 50 个州分别设计了不同的图案。其他硬币没有州设计,所以只有 25 美分硬币有这个额外的值。我们可以通过更改 Quarter 变体以包含内部存储的 UsState 值来将此信息添加到我们的 enum 中,我们在示例 6-4 中已经这样做了。
As an example, let’s change one of our enum variants to hold data inside it.
From 1999 through 2008, the United States minted quarters with different
designs for each of the 50 states on one side. No other coins got state
designs, so only quarters have this extra value. We can add this information to
our enum by changing the Quarter variant to include a UsState value
stored inside it, which we’ve done in Listing 6-4.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-04/src/main.rs:here}}
}
让我们想象一个朋友正在尝试收集所有 50 个州的 25 美分硬币。当我们按硬币类型分拣零钱时,我们还会喊出与每枚 25 美分硬币相关的州的名称,这样如果是我们朋友没有的,他们就可以将其添加到收藏中。
Let’s imagine that a friend is trying to collect all 50 state quarters. While we sort our loose change by coin type, we’ll also call out the name of the state associated with each quarter so that if it’s one our friend doesn’t have, they can add it to their collection.
在此代码的 match 表达式中,我们在匹配 Coin::Quarter 变体值的模式中添加了一个名为 state 的变量。当匹配到 Coin::Quarter 时,state 变量将绑定到该 25 美分硬币所属州的值。然后,我们可以在该分支的代码中使用 state,如下所示:
In the match expression for this code, we add a variable called state to the
pattern that matches values of the variant Coin::Quarter. When a
Coin::Quarter matches, the state variable will bind to the value of that
quarter’s state. Then, we can use state in the code for that arm, like so:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-09-variable-in-pattern/src/main.rs:here}}
}
如果我们调用 value_in_cents(Coin::Quarter(UsState::Alaska)),coin 将会是 Coin::Quarter(UsState::Alaska)。当我们将该值与每个 match 分支进行比较时,直到遇到 Coin::Quarter(state) 才会匹配。此时,state 的绑定将是 UsState::Alaska。然后我们可以在 println! 表达式中使用该绑定,从而从 Quarter 的 Coin 枚举变体中提取出内部的州值。
If we were to call value_in_cents(Coin::Quarter(UsState::Alaska)), coin
would be Coin::Quarter(UsState::Alaska). When we compare that value with each
of the match arms, none of them match until we reach Coin::Quarter(state). At
that point, the binding for state will be the value UsState::Alaska. We can
then use that binding in the println! expression, thus getting the inner
state value out of the Coin enum variant for Quarter.
Option<T> 的 match 模式 (The Option<T> match Pattern)
The Option<T> match Pattern
在上一节中,我们想在使用 Option<T> 时从 Some 情况下提取出内部的 T 值;我们也可以像处理 Coin 枚举那样使用 match 来处理 Option<T>!我们将比较 Option<T> 的变体,而不是硬币,但 match 表达式的工作方式保持不变。
In the previous section, we wanted to get the inner T value out of the Some
case when using Option<T>; we can also handle Option<T> using match, as
we did with the Coin enum! Instead of comparing coins, we’ll compare the
variants of Option<T>, but the way the match expression works remains the
same.
假设我们要编写一个函数,它接收一个 Option<i32>,如果有值,就在该值上加 1。如果没有值,函数应该返回 None 值,而不尝试执行任何操作。
Let’s say we want to write a function that takes an Option<i32> and, if
there’s a value inside, adds 1 to that value. If there isn’t a value inside,
the function should return the None value and not attempt to perform any
operations.
由于有了 match,这个函数非常容易编写,看起来就像示例 6-5 所示。
This function is very easy to write, thanks to match, and will look like
Listing 6-5.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:here}}
}
让我们更详细地研究一下 plus_one 的第一次执行。当我们调用 plus_one(five) 时,plus_one 体内的变量 x 将具有值 Some(5)。然后我们将其与每个 match 分支进行比较:
Let’s examine the first execution of plus_one in more detail. When we call
plus_one(five), the variable x in the body of plus_one will have the
value Some(5). We then compare that against each match arm:
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:first_arm}}
Some(5) 值与模式 None 不匹配,所以我们继续执行下一个分支:
The Some(5) value doesn’t match the pattern None, so we continue to the
next arm:
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:second_arm}}
Some(5) 与 Some(i) 匹配吗?匹配!我们拥有相同的变体。i 绑定到 Some 中包含的值,因此 i 取得值 5。然后执行 match 分支中的代码,因此我们在 i 的值上加 1,并创建一个内部包含总和 6 的新 Some 值。
Does Some(5) match Some(i)? It does! We have the same variant. The i
binds to the value contained in Some, so i takes the value 5. The code in
the match arm is then executed, so we add 1 to the value of i and create a
new Some value with our total 6 inside.
现在让我们考虑示例 6-5 中 plus_one 的第二次调用,其中 x 是 None。我们进入 match 并与第一个分支进行比较:
Now let’s consider the second call of plus_one in Listing 6-5, where x is
None. We enter the match and compare to the first arm:
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:first_arm}}
匹配了!没有可以加的值,所以程序停止并返回 => 右侧的 None 值。因为第一个分支匹配了,所以不再比较其他分支。
It matches! There’s no value to add to, so the program stops and returns the
None value on the right side of =>. Because the first arm matched, no other
arms are compared.
在许多情况下,将 match 和枚举结合使用非常有用。你会经常在 Rust 代码中看到这种模式:对枚举进行 match,将变量绑定到其中的数据,然后根据它执行代码。起初这有点棘手,但一旦你习惯了,你就会希望所有语言都有它。它一直是用户的最爱。
Combining match and enums is useful in many situations. You’ll see this
pattern a lot in Rust code: match against an enum, bind a variable to the
data inside, and then execute code based on it. It’s a bit tricky at first, but
once you get used to it, you’ll wish you had it in all languages. It’s
consistently a user favorite.
匹配是穷尽的 (Matches Are Exhaustive)
Matches Are Exhaustive
我们还需要讨论 match 的另一个方面:分支的模式必须涵盖所有可能性。考虑一下我们 plus_one 函数的这个版本,它有一个 bug,无法编译:
There’s one other aspect of match we need to discuss: The arms’ patterns must
cover all possibilities. Consider this version of our plus_one function,
which has a bug and won’t compile:
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/src/main.rs:here}}
我们没有处理 None 的情况,所以这段代码会导致一个 bug。幸运的是,这是一个 Rust 知道如何捕捉的 bug。如果我们尝试编译这段代码,我们会得到这个错误:
We didn’t handle the None case, so this code will cause a bug. Luckily, it’s
a bug Rust knows how to catch. If we try to compile this code, we’ll get this
error:
{{#include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/output.txt}}
Rust 知道我们没有涵盖每种可能的情况,甚至知道我们忘记了哪种模式!Rust 中的匹配是“穷尽的 (exhaustive)”:为了使代码有效,我们必须穷尽每一种可能性。特别是在 Option<T> 的情况下,当 Rust 阻止我们忘记显式处理 None 情况时,它保护我们不至于假设自己拥有一个值而实际上可能是 null,从而使前面讨论的“十亿美元错误”变得不可能。
Rust knows that we didn’t cover every possible case and even knows which
pattern we forgot! Matches in Rust are exhaustive: We must exhaust every last
possibility in order for the code to be valid. Especially in the case of
Option<T>, when Rust prevents us from forgetting to explicitly handle the
None case, it protects us from assuming that we have a value when we might
have null, thus making the billion-dollar mistake discussed earlier impossible.
通配模式与 _ 占位符 (Catch-All Patterns and the _ Placeholder)
Catch-All Patterns and the _ Placeholder
使用枚举,我们还可以对几个特定的值采取特殊行动,但对所有其他值采取一个默认行动。想象一下我们正在实现一个游戏,如果你在掷骰子时掷出 3,你的玩家不动,而是得到一顶华丽的新帽子。如果你掷出 7,你的玩家会失去一顶华丽的帽子。对于所有其他值,你的玩家在游戏板上移动相应的格数。下面是一个实现该逻辑的 match,其中骰子的结果是硬编码的而不是随机值,所有其他逻辑都由没有主体的函数表示,因为实际实现它们超出了本例的范围:
Using enums, we can also take special actions for a few particular values, but
for all other values take one default action. Imagine we’re implementing a game
where, if you roll a 3 on a dice roll, your player doesn’t move but instead
gets a fancy new hat. If you roll a 7, your player loses a fancy hat. For all
other values, your player moves that number of spaces on the game board. Here’s
a match that implements that logic, with the result of the dice roll
hardcoded rather than a random value, and all other logic represented by
functions without bodies because actually implementing them is out of scope for
this example:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-15-binding-catchall/src/main.rs:here}}
}
对于前两个分支,模式是字面量值 3 和 7。对于涵盖所有其他可能值的最后一个分支,模式是我们选择命名为 other 的变量。为 other 分支运行的代码通过将该变量传递给 move_player 函数来使用它。
For the first two arms, the patterns are the literal values 3 and 7. For
the last arm that covers every other possible value, the pattern is the
variable we’ve chosen to name other. The code that runs for the other arm
uses the variable by passing it to the move_player function.
这段代码可以编译,即使我们没有列出一个 u8 可以拥有的所有可能值,因为最后一个模式将匹配所有未明确列出的值。这种通配模式 (catch-all pattern) 满足了 match 必须穷尽的要求。请注意,我们必须将通配分支放在最后,因为模式是按顺序评估的。如果我们把通配分支放在前面,其他分支将永远不会运行,所以如果你在通配分支之后添加分支,Rust 会警告我们!
This code compiles, even though we haven’t listed all the possible values a
u8 can have, because the last pattern will match all values not specifically
listed. This catch-all pattern meets the requirement that match must be
exhaustive. Note that we have to put the catch-all arm last because the
patterns are evaluated in order. If we had put the catch-all arm earlier, the
other arms would never run, so Rust will warn us if we add arms after a
catch-all!
Rust 还拥有一个模式,当我们想要通配但不想“使用”通配模式中的值时可以使用:_ 是一个特殊的模式,它匹配任何值且不绑定到该值。这告诉 Rust 我们不打算使用该值,所以 Rust 不会就未使用的变量向我们发出警告。
Rust also has a pattern we can use when we want a catch-all but don’t want to
use the value in the catch-all pattern: _ is a special pattern that matches
any value and does not bind to that value. This tells Rust we aren’t going to
use the value, so Rust won’t warn us about an unused variable.
让我们改变游戏规则:现在,如果你掷出除 3 或 7 之外的任何数字,你必须重新掷骰子。我们不再需要使用通配值,所以我们可以将代码改为使用 _ 而不是名为 other 的变量:
Let’s change the rules of the game: Now, if you roll anything other than a 3 or
a 7, you must roll again. We no longer need to use the catch-all value, so we
can change our code to use _ instead of the variable named other:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-16-underscore-catchall/src/main.rs:here}}
}
这个例子也满足穷尽性要求,因为我们在最后一个分支中明确忽略了所有其他值;我们没有遗漏任何东西。
This example also meets the exhaustiveness requirement because we’re explicitly ignoring all other values in the last arm; we haven’t forgotten anything.
最后,我们将再次改变游戏规则,如果你掷出除 3 或 7 之外的任何数字,你的回合内不会发生任何其他事情。我们可以通过使用单元值(我们在 “元组类型” 部分提到的空元组类型)作为与 _ 分支配合的代码来表达这一点:
Finally, we’ll change the rules of the game one more time so that nothing else
happens on your turn if you roll anything other than a 3 or a 7. We can express
that by using the unit value (the empty tuple type we mentioned in “The Tuple
Type” section) as the code that goes with the _ arm:
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-17-underscore-unit/src/main.rs:here}}
}
在这里,我们明确告诉 Rust 我们不打算使用任何与前面分支不匹配的其他值,并且在这种情况下我们不想运行任何代码。
Here, we’re telling Rust explicitly that we aren’t going to use any other value that doesn’t match a pattern in an earlier arm, and we don’t want to run any code in this case.
关于模式和匹配,我们将在第 19 章涵盖更多内容。现在,我们要继续介绍 if let 语法,它在 match 表达式显得有点冗长的情况下非常有用。
There’s more about patterns and matching that we’ll cover in Chapter
19. For now, we’re going to move on to the
if let syntax, which can be useful in situations where the match expression
is a bit wordy.