这match
控制流构造
Rust 有一个非常强大的控制流结构,称为match
那
允许您将值与一系列模式进行比较,然后执行
基于哪个模式匹配的代码。模式可以由文字值、
变量名称、通配符和许多其他内容;章
18 涵盖了所有不同种类的图案
以及他们做什么。的力量match
来自
模式以及编译器确认所有可能的情况都是
处理。
想想match
像硬币分拣机一样的表情:硬币滑动
沿着一条带有大小不一的孔的轨道向下,每枚硬币都会落下
它遇到的第一个适合的洞。同样,值会变为
通过match
,在第一个模式中,值为“fits”,
该值属于要在执行期间使用的关联代码块。
说到硬币,让我们以它们为例,使用match
!我们可以编写一个
函数,它采用未知的美国硬币,并且以类似于计数的方式
machine 确定它是哪种硬币并返回以美分为单位的值,如图所示
在示例 6-3 中。
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } fn main() {}
示例 6-3:一个枚举和一个match
表达式,该表达式具有
枚举的变体作为其模式
让我们分解一下match
在value_in_cents
功能。首先我们列出
这match
keyword 后跟一个表达式,在本例中为值coin
.这似乎与if
但
有一个很大的区别:使用if
,则条件需要计算为
Boolean 值,但在这里它可以是任何类型。的类型coin
在此示例中
是Coin
enum 中。
接下来是match
武器。arm 有两个部分:一个模式和一些代码。这
这里的第一个分支有一个模式,该模式是Coin::Penny
然后是分隔模式和要运行的代码的运算符。本例中的代码
只是价值=>
1
.每个分支都用逗号分隔。
当match
expression 执行时,它会将结果值与
每个臂的模式,按顺序排列。如果模式与该值匹配,则代码
将执行与该模式关联的模式。如果该模式与
value 时,执行会继续到下一个分支,就像在硬币分拣机中一样。
我们需要多少个 arm:在示例 6-3 中,我们的match
有四只手臂。
与每个分支关联的代码是一个表达式,结果值
匹配 Arm 中的 expression 是为
整个match
表达。
如果 match 臂代码很短,我们通常不会使用大括号,因为它确实如此
在示例 6-3 中,每个 arm 只返回一个值。如果要运行多个
代码行,则必须使用大括号和逗号
然后,跟随 ARM 是可选的。例如,以下代码打印
“Lucky penny!” 每次使用Coin::Penny
,但仍然
返回块的最后一个值,1
:
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => { println!("Lucky penny!"); 1 } Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } fn main() {}
绑定到值的模式
match arms 的另一个有用功能是它们可以绑定到 与模式匹配的值。这就是我们从 enum 中提取值的方法 变种。
例如,让我们更改其中一个枚举变体以在其中保存数据。
从 1999 年到 2008 年,美国铸造了具有不同
一侧为 50 个州中的每一个州设计。没有其他硬币进入状态
设计,因此只有 Quarter 具有此额外值。我们可以将此信息添加到
我们enum
通过将Quarter
variant 中,以包含UsState
价值
存储在其中,我们在示例 6-4 中已经完成了。
#[derive(Debug)] // so we can inspect the state in a minute enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn main() {}
示例 6-4:一个Coin
枚举,其中Quarter
变体
还持有UsState
价值
假设一个朋友正在尝试收集所有 50 个州的 25 美分硬币。而 我们按硬币类型对零钱进行排序,我们还将叫出 state 与每个季度相关联,因此如果它是我们的朋友没有的,则 他们可以将其添加到他们的收藏中。
在此代码的 match 表达式中,我们添加了一个名为state
到
匹配变体值的模式Coin::Quarter
.当Coin::Quarter
matches、state
变量将绑定到该
季度的状态。然后我们可以使用state
在该 Arm 的代码中,如下所示:
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("State quarter from {state:?}!"); 25 } } } fn main() { value_in_cents(Coin::Quarter(UsState::Alaska)); }
如果我们调用value_in_cents(Coin::Quarter(UsState::Alaska))
,coin
将Coin::Quarter(UsState::Alaska)
.当我们将该值与每个
的火柴臂中,在我们到达之前,它们都没有匹配Coin::Quarter(state)
.在
该点,则state
将是值UsState::Alaska
.我们可以
然后在println!
表达式,从而得到内部的
state 值从Coin
enum 变体Quarter
.
匹配Option<T>
在上一节中,我们想获取内部的T
值从Some
case when usingOption<T>
;我们也可以处理Option<T>
用match
如
我们用Coin
enum!我们不是比较硬币,而是比较
的变体Option<T>
,但是match
表达式有效
相同。
假设我们想编写一个函数,该函数采用Option<i32>
并且,如果
里面有一个值,将该值加 1。如果里面没有值,
该函数应返回None
值,并且不会尝试执行任何
操作。
这个函数很容易编写,多亏了match
,如下所示
示例 6-5.
fn main() { fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None); }
示例 6-5:使用match
表达式开启
一Option<i32>
让我们看看plus_one
更详细地。当我们调用plus_one(five)
、变量x
在plus_one
将具有
价值Some(5)
.然后,我们将其与每个匹配组进行比较:
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
这Some(5)
值与模式不匹配None
,因此我们继续执行
下一个 Arm:
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
DoesSome(5)
火柴Some(i)
?确实如此!我们有相同的变体。这i
绑定到 中包含的值Some
所以i
取值5
.中的代码
然后执行 match arm,因此我们将 1 添加到i
并创建一个
新增功能Some
值替换为我们的总计6
里面。
现在让我们考虑plus_one
在示例 6-5 中,其中x
是None
.我们输入match
并与第一只手臂进行比较:
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
它匹配!没有可添加的值,因此程序停止并返回None
值位于 . 的右侧。因为第一个手臂匹配,所以没有其他
手臂进行比较。=>
结合match
枚举在许多情况下都很有用。您将看到以下内容
pattern 中:match
针对枚举,将变量绑定到
data 中,然后基于它执行代码。一开始有点棘手,但是
一旦你习惯了它,你会希望你拥有所有语言的版本。它
一直是用户的最爱。
匹配项详尽无遗
还有另一个方面match
我们需要讨论:手臂的图案必须
涵盖所有可能性。考虑一下我们的plus_one
功能
它有一个 bug 并且不会编译:
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
我们没有处理None
case,所以这段代码会导致一个 bug。幸运的是,它是
Rust 知道如何捕捉的 bug。如果我们尝试编译这段代码,我们会得到这个
错误:
$ cargo run
Compiling enums v0.1.0 (file:///projects/enums)
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:15
|
3 | match x {
| ^ pattern `None` not covered
|
note: `Option<i32>` defined here
--> /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/option.rs:574:1
::: /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/option.rs:578:5
|
= note: not covered
= note: the matched value is of type `Option<i32>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
4 ~ Some(i) => Some(i + 1),
5 ~ None => todo!(),
|
For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums` (bin "enums") due to 1 previous error
Rust 知道我们没有涵盖所有可能的情况,甚至知道哪种情况
我们忘了的模式!Rust 中的匹配项是详尽的:我们必须穷尽每一个
的可能性,才能使代码有效。尤其是在Option<T>
,当 Rust 防止我们忘记显式处理None
case 中,它可以保护我们避免在可能的时候假设我们有价值
为 null,因此之前讨论的 10 亿美元错误变得不可能。
Catch-all 模式和占位符_
使用枚举,我们也可以对一些特定值采取特殊作,但是
对于所有其他值,请执行一个 default作。想象一下我们正在实现一个游戏
其中,如果您在掷骰子时掷出 3,您的玩家不会移动,而是
得到一顶新的花哨帽子。如果您掷出 7,您的玩家将失去一顶花哨的帽子。对于所有人
other 值时,您的玩家会在游戏板上移动该数量的空格。这是
一个match
实现该逻辑,以及掷骰子的结果
hardcoded 而不是 random 值,所有其他 logic 由
函数,因为实际实现它们超出了
此示例:
fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), other => move_player(other), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn move_player(num_spaces: u8) {} }
对于前两个分支,模式是文本值3
和7
.为
覆盖所有其他可能值的最后一个分支,模式是
变量other
.为other
手臂
通过将其传递给move_player
功能。
这段代码可以编译,即使我们没有列出所有可能的值u8
可以有,因为最后一个模式将匹配所有值,而不是专门匹配
上市。此 catch-all 模式满足以下要求match
必须是
详尽。请注意,我们必须将 catch-all 臂放在最后,因为
按顺序评估模式。如果我们把 catch-all 臂放在前面,另一个
arms 永远不会运行,所以如果我们在 catch-all 之后添加 arms,Rust 会警告我们!
Rust 还有一个模式,当我们想要一个 catch-all 但不想使用 catch-all 模式中的值时,我们可以使用:是一个特殊的模式,它匹配
any 值,并且不绑定到该值。这告诉 Rust 我们不会
使用该值,这样 Rust 就不会警告我们未使用的变量。_
让我们改变游戏规则:现在,如果您掷出 3 或
A 7,您必须再次掷骰子。我们不再需要使用 catch-all 值,因此我们
可以更改我们的代码以代替名为_
other
:
fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {} }
此示例还满足穷举性要求,因为我们显式地 忽略最后一个分支中的所有其他值;我们没有忘记任何东西。
最后,我们将再次更改游戏规则,以便没有其他内容
如果你掷出 3 或 7 以外的任何东西,就会在你的回合发生。我们可以表达
通过使用 unit 值(我们在“元组
Type“ 部分)作为 arm 附带的代码:_
fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {} }
在这里,我们明确告诉 Rust 我们不会使用任何其他值 这与早期 ARM 中的模式不匹配,我们不想运行任何 code 中。
我们将在 章节 中介绍更多关于 pattern 和 matching 的信息
18. 现在,我们将继续讨论if let
语法,这在match
表达
有点啰嗦。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准