6.3 控制流运算符-match
6.3.1. 什么是match?
match允许一个值与一系列模式进行比较,并执行与匹配模式对应的代码。模式可以是字面值、变量名、通配符等等。
把match表达式想象成一台硬币分类机:硬币沿着带有不同大小孔洞的轨道滑下,每枚硬币都会从它遇到的第一个合适的孔洞落下。同样地,一个值会依次经过match中的每个模式,当它“适合”第一个模式时,就会落入执行时要使用的关联代码块中。
6.3.2. match的实际应用
来看个例子:编写一个函数,接受一枚未知的美国硬币,并以类似计数机的方式判断它是哪种硬币,然后返回其价值(以美分为单位)。
#![allow(unused)]
fn main() {
enum Coin {
Penny,// 1美分
Nickel,// 5美分
Dime,// 10美分
Quarter,// 25美分
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
-
match关键字后面跟着一个表达式,在本例中就是值coin。这看起来与if使用的条件表达式很像,但有一个很大的区别:if的条件必须是布尔值,而match可以处理任何类型。本例中coin的类型是我们在第一行定义的Coin枚举。 -
接下来是花括号。花括号里有四个分支(英文叫arm),每个分支都由待匹配的模式和对应的代码组成。第一个分支
Coin::Penny => 1,使用Coin::Penny作为模式。=>用来分隔模式和要运行的代码,这里要运行的代码就是值1,也就是返回1。不同分支之间用逗号分隔。 -
当
match表达式运行时,它会把match后面的表达式——这里是coin——从上到下依次与各个分支比较。如果某个模式与值匹配,就执行与该模式关联的代码;如果不匹配,就继续检查下一个分支。匹配成功的分支所对应的代码表达式会作为整个match表达式的值返回。 例如,如果match匹配到5美分硬币,也就是Coin::Nickel,那么整个表达式的结果就是5。又因为match表达式是value_in_cents中的最后一个表达式,所以它的值——5——会作为函数的返回值。 -
这里每个分支的代码都很简单,所以用
=>就够了。但如果某个分支包含多行代码,就需要用花括号把这些行包起来。例如:
#![allow(unused)]
fn main() {
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
6.3.3. 绑定值的模式
match的分支可以绑定到被匹配值的一部分,从而可以从枚举变体中提取值。
例如,一位朋友正在尝试收集全部50个州的25美分硬币。当我们按硬币类型对零钱分类时,还会标注每个25美分硬币关联的州名(美国州太多了,这里只写了Alabama和Alaska):
#[derive(Debug)] // 便于调试打印
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
let c = Coin::Quarter(UsState::Alaska);
println!("{}", value_in_cents(c));
}
-
给代表25美分硬币的
Coin变体关联一份数据,也就是上面的UsState枚举。 -
在
value_in_cents函数中,Quarter分支也需要相应调整。匹配模式从Coin::Quarter改成Coin::Quarter(state),意思是把Coin::Quarter关联的值绑定到变量state上,这样在后面的代码块中就可以使用这个关联值。 有些情况下可能不需要Coin::Quarter关联的值。这时可以用通配符_表示不关心内容:Coin::Quarter(_) -
在
main中先声明变量c,存的是Coin::Quarter(UsState::Alaska)。也就是说,它保存了Coin::Quarter变体,关联值是UsState::Alaska变体。然后调用value_in_cents。
来看输出:
State quarter from Alaska!
25
6.3.4. 匹配Option<T>
来分析上一篇文章 6.2. Option枚举 最后的代码示例:
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = match y {
Some(value) => x + value, // 如果 y 是 Some,则解包并相加
None => x, // 如果 y 是 None,则返回 x
};
}
- 如果
y不是None,就解包,把Some关联的值绑定到value,并返回x + value。 - 如果
y是None,就只返回x的值。
6.3.5. match必须穷尽
Rust要求match覆盖所有可能性,这样才能保证代码安全有效。
对上一段代码稍作修改:
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = match y {
Some(value) => x + value,
};
}
输出:
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:5:21
|
5 | let sum = match y {
| ^ pattern `None` not covered
|
note: `Option<i8>` defined here
--> /Users/stanyin/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:600:1
|
600 | pub enum Option<T> {
| ^^^^^^^^^^^^^^^^^^
...
604 | None,
| ---- not covered
= note: the matched value is of type `Option<i8>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
6 ~ Some(value) => x + value,
7 ~ None => todo!(),
|
Rust发现没有覆盖None这种可能性,所以报错。一旦补上处理None的分支,就没问题了。
如果可能性太多,或者你不想处理其中一些情况,可以使用通配符_。
6.3.6. 通配符
先照常写出你想处理的分支,其余情况用通配符_代替。
例如:v是一个u8变量,我们想判断v是否为0。
use rand::Rng; // 使用外部 crate
fn main(){
let v: u8 = rand::thread_rng().gen_range(0..=255); // 生成随机数
println!("{}", v);
match v {
0 => println!("zero"),
_ => println!("not zero"),
}
}
u8有256种可能的值,用match自然不可能为每个值都写一个分支。因此可以为0写一个分支,其余情况用通配符_代替。
输出:
133
not zero