参考章节《Rust程序设计语言》第18.3章 模式语法
模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。
我们先来看看模式匹配的语法
- 匹配字面值
1
2
3
4
5
6
7
8
9
10
|
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
|
这段代码会打印 one
因为 x
的值是 1
。
- 匹配命名变量
1
2
3
4
5
6
7
8
9
|
fn main() {
let x = Some(5);
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y), // 匹配
_ => println!("Default case, x = {:?}", x),
}
}
|
第一个匹配分支的模式并不匹配 x
中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 y
,它会匹配任何 Some
中的值。
因此这个 y
绑定了 x
中 Some
内部的值。这个值是 5
,所以这个分支的表达式将会执行并打印出 Matched, y = 5
。
- 匹配多个模式
1
2
3
4
5
6
7
8
9
|
fn main() {
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
|
在 match
表达式中,可以使用 |
语法匹配多个模式,它代表 或(or)
的意思。上面的代码会打印 one or two
。
- 范围匹配
1
2
3
4
5
6
7
8
|
fn main() {
let x = 5;
match x {
1..=5 => println!("one through five"), // 表示匹配 1 到 5 的值
_ => println!("something else"),
}
}
|
..=
语法允许你匹配一个闭区间范围内的值。
如果 x
是 1、2、3、4
或 5
,第一个分支就会匹配。这相比使用 |
运算符表达相同的意思更为方便
范围只允许用于数字
或 char
值,因为编译器会在编译时检查范围不为空。
char
和 数字值
是 Rust 仅有的可以判断范围是否为空的类型。
- 解构结构体
1
2
3
4
5
6
7
8
9
10
11
12
|
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p; // 将结构体中 x 和 y 的值,解构到变量 a 和 b 中
assert_eq!(0, a);
assert_eq!(7, b);
}
|
这段代码创建了变量 a
和 b
来匹配结构体 p
中的 x
和 y
字段。模式中的变量名不必与结构体中的字段名一致。
1
2
3
4
5
6
7
8
9
|
// 省略结构体定义
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p; // 将结构体中 x 和 y 的值,解构到新变量 x 和 y 中
assert_eq!(0, x);
assert_eq!(7, y);
}
|
只需列出结构体字段的名称,则模式创建的变量会有相同的名称。
1
2
3
4
5
6
7
8
9
10
11
|
// 省略结构体定义
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {}", x), // y === 0 的情况
Point { x: 0, y } => println!("On the y axis at {}", y), // x === 0 的情况
Point { x, y } => println!("On neither axis: ({}, {})", x, y), // 其他
}
}
|
- 解构枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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 {} and in the y direction {}", x, y);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {}, green {}, and blue {}", r, g, b)
}
}
}
|
一个细节是解构枚举的模式需要对应枚举所定义的储存数据的方式
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color), // 嵌套一个 Color 枚举
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change the color to red {}, green {}, and blue {}", r, g, b)
}
Message::ChangeColor(Color::Hsv(h, s, v)) => println!(
"Change the color to hue {}, saturation {}, and value {}",
h, s, v
),
_ => (),
}
}
|
- 忽略模式中的值
1
2
3
4
5
6
7
|
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
。
1
2
3
4
5
6
7
8
9
|
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
会被忽略。
1
2
3
4
|
fn main() {
let _x = 5;
let y = 10;
}
|
这会得到警告说未使用变量 y
,不过没有警告说未使用下划线开头的变量。
- 用
..
忽略剩余值
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
}
|
对于有多个部分的值,可以使用 ..
语法来只使用部分并忽略其它值,避免不得不每一个忽略值列出下划线。
1
2
3
4
5
6
7
8
9
|
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
}
}
}
|
这里用 first
和 last
来匹配第一个和最后一个值。而 ..
将匹配并忽略中间的所有值。
1
2
3
4
5
6
7
8
9
|
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
|
上面的例子会报错,因为它是 有歧义的
,Rust 不可能决定在元组中匹配 second
值之前应该忽略多少个值,以及在之后忽略多少个值。
- 利用匹配守卫提供的额外条件
匹配守卫(match guard)是一个指定于 match
分支模式之后的额外 if
条件,它也必须被满足才能选择此分支。
1
2
3
4
5
6
7
8
9
|
fn main() {
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
}
|
上面的例子会打印出 less than five: 4
。当 num
与模式中第一个分支比较时,因为 Some(4)
匹配 Some(x)
所以可以匹配。
接着匹配守卫检查 x
值是否小于 5
,因为 4
小于 5
,所以第一个分支被选择。
@
绑定
at
运算符(@)
允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7, // 测试 Message::Hello 的 id 字段是否位于 3..=7 范围内,同时也希望能将其值绑定到 id_variable 变量中,以便此分支相关联的代码可以使用它。
} => println!("Found an id in range: {}", id_variable),
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_variable @
,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
使用 @
可以在一个模式中同时测试和保存变量值。
总结一下
模式匹配非常多,我的建议是,先看2 - 3
遍,混个脸熟,使用时当作工具书来查,用的多了自然就记住了。