match
match
Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;match 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。可以把 match 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 match 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
let day = 5;
match day {
0 | 6 => println!("weekend"),
1 ... 5 => println!("weekday"),
_ => println!("invalid"),
}
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,
}
}
其中 | 用于匹配多个值,…匹配一个范围 (包含最后一个值),并且 _
在这里是必须的,因为 match 强制进行穷尽性检查 (exhaustiveness checking),必须覆盖所有的可能值。
通配符
但是您必须匹配所有可能的结果。例如,这将不起作用:
fn main() {
let my_number: u8 = 5;
match my_number {
0 => println!("it's zero"),
1 => println!("it's one"),
2 => println!("it's two"),
// ⚠️
}
}
error[E0004]: non-exhaustive patterns: `3u8..=std::u8::MAX` not covered
--> src\main.rs:3:11
|
3 | match my_number {
| ^^^^^^^^^ pattern `3u8..=std::u8::MAX` not covered
Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,u8 可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式 _
替代:
fn main() {
let my_number: u8 = 5;
match my_number {
0 => println!("it's zero"),
1 => println!("it's one"),
2 => println!("it's two"),
_ => println!("It's some other number"),
}
}
_
模式会匹配所有的值。通过将其放置于其他分支之后,_
将会匹配所有之前没有指定的可能的值。()
就是 unit 值,所以 _
的情况什么也不会发生。因此,可以说我们想要对 _
通配符之前没有列出的所有可能的值不做任何处理。
返回值
您可以声明一个具有匹配项的值:
fn main() {
let my_number = 5;
let second_number = match my_number {
0 => 0,
5 => 10,
_ => 2,
};
}
匹配项必须返回相同的类型。所以你不能这样做:
fn main() {
let my_number = 10;
let some_variable = match my_number {
10 => 8,
_ => "Not ten", // ⚠️
};
}
error[E0308]: `match` arms have incompatible types
--> src\main.rs:17:14
|
15 | let some_variable = match my_number {
| _________________________-
16 | | 10 => 8,
| | - this is found to be of type `{integer}`
17 | | _ => "Not ten",
| | ^^^^^^^^^ expected integer, found `&str`
18 | | };
| |_____- `match` arms have incompatible types
fn main() {
let some_variable = if my_number == 10 { 8 } else { "something else "}; // ⚠️
}
但这有效,因为您有一个不同的 let 语句。
fn main() {
let my_number = 10;
if my_number == 10 {
let some_variable = 8;
} else {
let some_variable = "Something else";
}
}
匹配 Option
我们在之前的部分中使用 OptionOption<i32>
,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None 值,而不尝试执行任何操作。
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);
让我们更仔细地检查 plus_one
的第一行操作。当调用 plus_one(five)
时,plus_one
函数体中的 x
将会是值 Some(5)
。接着将其与每个分支比较。
None => None,
值 Some(5)
并不匹配模式 None
,所以继续进行下一个分支。
Some(i) => Some(i + 1),
Some(5)
与 Some(i)
匹配吗?当然匹配!它们是相同的成员。i
绑定了 Some
中包含的值,所以 i
的值是 5
。接着匹配分支的代码被执行,所以我们将 i
的值加一并返回一个含有值 6
的新 Some
。
接着考虑下示例 6-5 中 plus_one
的第二个调用,这里 x
是 None
。我们进入 match
并与第一个分支相比较。
None => None,
匹配上了!这里没有值来加一,所以程序结束并返回 =>
右侧的值 None
,因为第一个分支就匹配到了,其他的分支将不再比较。将 match
与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:match
一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。这在一开始有点复杂,不过一旦习惯了,你会希望所有语言都拥有它!这一直是用户的最爱。
元组匹配
您看到分号结尾了吗?这是因为,匹配结束后,我们实际上告诉编译器:let second_number = 10;
。您也可以匹配更复杂的东西。您使用一个元组来做到这一点。
fn main() {
let sky = "cloudy";
let temperature = "warm";
match (sky, temperature) {
("cloudy", "cold") => println!("It's dark and unpleasant today"),
("clear", "warm") => println!("It's a nice day"),
("cloudy", "warm") => println!("It's dark but not bad"),
_ => println!("Not sure what the weather is."),
}
}
match 表达式也可以用于解构元组:
let pair = (0, -2);
match pair {
(0, y) => println!("x is `0` and `y` is `{:?}`", y),
(x, 0) => println!("`x` is `{:?}` and y is `0`", x),
_ => println!("It doesn't matter what they are"),
}
您甚至可以在 match 中放入 if。
fn main() {
let children = 5;
let married = true;
match (children, married) {
(children, married) if married == false => println!("Not married with {} children", children),
(children, married) if children == 0 && married == true => println!("Married but no children"),
_ => println!("Married? {}. Number of children: {}.", married, children),
}
}
您可以在匹配中任意多次使用 _
。在这种颜色匹配中,我们有三个,但一次只能检查一个。
fn match_colours(rbg: (i32, i32, i32)) {
match rbg {
(r, _, _) if r < 10 => println!("Not much red"),
(_, b, _) if b < 10 => println!("Not much blue"),
(_, _, g) if g < 10 => println!("Not much green"),
_ => println!("Each colour has at least 10"),
}
}
fn main() {
let first = (200, 0, 0);
let second = (50, 50, 50);
let third = (200, 50, 0);
match_colours(first);
match_colours(second);
match_colours(third);
}
Not much blue
Each colour has at least 10
Not much green
这也显示了 match 语句的工作方式,因为在第一个示例中,它仅打印了不多的蓝色。但是首先也没有太多绿色。match 语句在找到匹配项时总是停止,并且不检查其余部分。这是一个很好的代码示例,可以很好地编译,但不是您想要的代码 match 的这种解构同样适用于结构体或者枚举。如果有必要,还可以使用 .. 来忽略域或者数据:
struct Point {
x: i32,
y: i32,
}
let origin = Point { x: 0, y: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
enum OptionalInt {
Value(i32),
Missing,
}
let x = OptionalInt::Value(5);
match x {
// 这里是 match 的 if guard 表达式,我们将在以后的章节进行详细介绍
OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"),
OptionalInt::Value(..) => println!("Got an int!"),
OptionalInt::Missing => println!("No such luck."),
}
变量绑定
您也可以在需要时使用 @ 来使用匹配表达式的值。在此示例中,我们在函数中匹配了 i32 输入。如果是 4 或 13,我们想在 println 中使用该数字。否则,我们不需要使用它。
fn match_number(input: i32) {
match input {
number @ 4 => println!("{} is an unlucky number in China (sounds close to 死)!", number),
number @ 13 => println!("{} is unlucky in North America, lucky in Italy! In bocca al lupo!", number),
_ => println!("Looks like a normal number"),
}
}
fn main() {
match_number(50);
match_number(13);
match_number(4);
}
let x = 1;
match x {
e @ 1 ... 5 => println!("got a range element {}", e),
_ => println!("anything"),
}
使用 ref 关键字来得到一个引用:
let x = 5;
let mut y = 5;
match x {
// the `r` inside the match has the type `&i32`
ref r => println!("Got a reference to {}", r),
}
match y {
// the `mr` inside the match has the type `&i32` and is mutable
ref mut mr => println!("Got a mutable reference to {}", mr),
}