在C语言系里,包括C++、C#、Java、JavaScript等超级流行的语言,以及Go、Swift等新兴语言也在用switch-case语句进行条件分支匹配,而Rust使用了一种新的条件分支匹配的模式match,类似于Kotlin中的when,但Rust中的match更加强劲。开始有些时候感觉还是switch比较方便,但用习惯了会觉得match真的很爽,太方便。
定义
在Rust中,match表达式是一种用于模式匹配和控制流的结构。它允许您根据给定值的不同模式执行不同的代码块,提供了超级灵活和强劲的模式匹配功能。
基本语法:
match value {
pattern1 => { /* block */ },
pattern2 => { /* block */ },
pattern3 if condition => { /* block */ },
_ => { /* default block */ }
}
其中:
- value 是需要进行模式匹配的值。
- pattern1、pattern2 等是不同的模式,用于匹配 value 的可能取值。
- condition 是可选的条件,可以在模式匹配的基础上进一步筛选匹配的情况。
- _ 是通配符,用于匹配所有其他情况。
- 大括号 {} 内的代码块包含了在模式匹配成功时需要执行的代码。
例如:
fn common_match() {
let num = 1;
match num {
1 => println!("this num is 壹"),
2 => println!("this num is 贰"),
3..=10 => println!("this num is {}", num),
_ => println!("this num is {}", num)
}
}
不过个人不太喜爱这里的_作为默认分支,简洁倒是够简洁,这可能也是Rust的核心设计思想之一,尽量让程序员少敲几个字符,但可读性稍微就差了一些,尤其是对于历史项目和新学习Rust的程序员不太友善。希望后面还是能把default关键词给支持一下,或者来个others,这样更接近自然语言的表达。
匹配臂
匹配臂(match arm) = 模式(Pattern) + 代码块(Block)
- 模式:用于匹配待匹配值的结构或者特征。
- 代码块:匹配成功后执行的代码块。
模式
模式是一种用于匹配和解构数据的语法格式。不止用于match表达式,还运用在if let表达式、for循环的解构中。其作用在于简单、清晰地处理不同的数据结构和情况。
常见的匹配模式有:
- 常量模式(字面量模式):直接匹配常量值。
- 范围模式:匹配范围,如 0..=10。
- 通配符模式:使用 _ 匹配任意值。
常量模式
fn constant_match() {
let number = 100;
match number {
1 => println!("number is 1"),// 常量匹配
2..=10 => println!("number is {}", number),//范围匹配
_ => println!("number is {}", number)//通配符匹配
}
}
枚举模式
匹配枚举的变体。
fn enum_match() {
enum Fruit {
Apple,
Orange,
Banana
}
let favorite_fruit = Fruit::Apple;
match favorite_fruit {
Fruit::Apple => println!("我喜爱吃苹果"),
Fruit::Orange => println!("我喜爱吃橙子"),
Fruit::Banana => println!("我喜爱吃香蕉")
}
}
Rust的核心理念是保障软件的可靠性,所以match对于enum类型的检查也更严格,匹配臂必须包含全部的enum值,或者通过_通配符来匹配没有匹配到的情况(不提议)。而C/C++/Java等都没有严格要求,但IDE有时可以通过lint插件提升代码质量。而Rust把模式的严格性写进了语言中,骨子里对可靠性进行严格要求,尽最大的可能性让程序员不要忘记处理业务逻辑。
变量模式
使用变量名匹配,并将值绑定到变量上。这里它实则有一个专有名词,叫模式绑定,即可以从模式中取出绑定的值
fn variable_match() {
let num = Some(600);
match num {
Some(v) => println!("num is {}", v),
None => println!("未匹配到值")
}
}
这个特性实则是和Result/Option的结合使用的,在匹配Option/Result时,如果匹配成功,会返回Option/Result中的值,如果匹配失败,会返回None/Err。如果Result/Option中有返回值可以把值取出进行后续的处理。这样就不会感觉到使用unwrap来取值那么麻烦。if-let用于Result更简洁一些,对于Option还是match方便,而且便于日后扩展。
注意,match的模式匹配也会交出所有权的,这里的num匹配守了num就没有值了,不能再使用了。
引用模式
对于只读的模式处理,使用 & 匹配引用,这样match不会带走所有权。
fn reference_match() {
let value = 42;
match &value {
&x => println!("Got a reference to {}", x),
}
match &value {
ref y => println!("Got a reference to {}", y),
}
}
这里ref进一步声明y是引用,只是声明一个变量,而不是定义一个变量,也不会遵守借用规则带走所有权。
结构体模式
按结构体的字段来匹配,所有字段都必须匹配才成功。
fn struct_match() {
struct Coordinate {
lon: f64,
lat: f64,
}
let coord = Coordinate { lon: 116.420941, lat: 39.922018 };
match coord {
Coordinate { lon, lat } => println!("lon: {}, lat: {}", lon, lat)
}
}
元组模式
匹配元组的元素,所有元素都匹配才成功。
fn tuple_match() {
let tuple = (1, 2, 3);
match tuple {
(x, y, z) => println!("x: {}, y: {}, z: {}", x, y, z),
}
}
判断模式
使用 if 关键字添加额外条件,如 pattern if condition。
概念上,我们把这种情况叫做匹配守卫。模式守卫出目前模式的后面,由关键字 if 后面的布尔类型表达式组成。当模式匹配成功时,将执行匹配守卫表达式。如果此表达式的计算结果为真,则此模式将进一步被确认为匹配成功。 否则,匹配将测试下一个模式,包括测试同一匹配臂中运算符 | 分割的后续匹配模式。
fn condition_match() {
let month = 2;
let year = 2024;
let days = match month {
2 if year % 4 == 0 => 29,
2 => 28,
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
_ => 0
};
println!("this month has {} days", days)
}
这个特性超级好用,在C语言中常常不加break,让多个case语句运行到第一个匹配的case,然后继续运行。这样在后续维护中超级容易出错,而Rust则要求所有case必须全部列出,对于多个匹配臂使用一个处理模式时可以使用|并列在一起,超级贴心。
注意事项
- 注意匹配臂的排列顺序。
match 表达式的执行流程是从上至下逐个尝试每个模式
,直到找到匹配的模式为止。一旦找到匹配的模式,将执行对应的代码块,并且整个 match 表达式的值就是执行代码块的结果值。
fn match_sequence() {
let y = Some(3);
match y {
Some(v) => println!("去泰山"),
Some(3) => println!("去黄山"),
None => println!("在家待着")
}
}
warning: unreachable pattern
--> src/main.rs:120:9
|
120 | Some(3) => println!("去黄山"),
| ^^^^^^^
|
= note: `#[warn(unreachable_patterns)]` on by default
- 单个匹配臂中可以匹配多个条件,可以使用操作符 | 连接多个匹配模式。 每个模式将按照从左到右的顺序进行测试,直到找到一个成功的匹配。
- 穷尽匹配。match表达式的匹配必须穷尽所有情况。
- 通配符。通过将 _ 其放置于其他分支后, 将会匹配所有遗漏的值。()
表明返回单元类型与所有分支返回值的类型一样,所以当匹配到 _ 后,什么也不会发生。除了_通配符,用一个变量来承载其他情况也是可以的。
fn multiple_match() {
let month = 10;
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => println!("有31天"),
4 | 6 | 9 | 11 => println!("有30天"),
2 => println!("不必定是28天还是29天,看年份"),
// _ => ()
// _other => ()
// other_number => println!("没有{}这个月份", other_number)
// other_number => ()
}
}
error[E0004]: non-exhaustive patterns: `i32::MIN..=0_i32` and `13_i32..=i32::MAX` not covered
--> src/main.rs:128:11
|
128 | match month {
| ^^^^^ patterns `i32::MIN..=0_i32` and `13_i32..=i32::MAX` not covered
|
= note: the matched value is of type `i32`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
|
131 ~ 2 => println!("不必定是28天还是29天,看年份"),
132 ~ i32::MIN..=0_i32 | 13_i32..=i32::MAX => todo!(),
|
- 每个匹配臂要返回一样的类型。由于match是一种表达式,它是可以用来赋值的,叫做匹配赋值
。如果每个匹配臂的返回类型不能保持一致,那么整个表达式所返回的类型就不能确定,这是有悖于rust的设计原则的,编译无法通过。
fn result_type() {
let value = 10;
let result = match value {
0 => "zero", // 字符串类型
1 => 1, // 整数类型
_ => false, // 布尔类型
};
println!("Result: {:?}", result);
}
error[E0308]: `match` arms have incompatible types
--> src/main.rs:142:14
|
140 | let result = match value {
| __________________-
141 | | 0 => "zero", // 字符串类型
| | ------ this is found to be of type `&str`
142 | | 1 => 1, // 整数类型
| | ^ expected `&str`, found integer
143 | | _ => false, // 布尔类型
144 | | };
| |_____- `match` arms have incompatible types


