
模式匹配,增强版的 switch
- 可以匹配各种类型(不只是整数或字符串)
- 能解构复杂的数据结构(比如元组、枚举、结构体)
- 是表达式,有返回值
- 要求穷尽所有可能(不会漏掉情况)
语法规则
match 要匹配的值 {
模式1 => {...},
模式2 => {...},
模式3 => {...},
...
}
✅ 整数匹配
#[test]
fn match_script() {
let number = 5;
match number {
1 => println!("1111"),
2 => println!("2222"),
3..=7 => println!("闭区间"), // 3 4 5 6 7
9..13 => println!("半开区间"), // 9 10 11 12
_ => println!("default"), // 通配符,匹配其余所有
}
}
_ 表示通配符,匹配其余所有,3..=7 3..7 区间写法
✅ 字符切片匹配
#[test]
fn match_script() {
let input = "yes"; // &str 可拷贝类型
match input {
"yes" => println!("Affirmative!"),
"no" => println!("Negative!"),
_ => println!("Unknown response"),
}
println!("input: {}", input); // 可正常输出
}
和整数匹配一样,匹配完所有权也还在
✅ 枚举匹配
#[derive(Debug)]
enum Rgb {
Red,
Green,
Blue,
}
#[test]
fn match_script() {
let color = Rgb::Red;
match color {
Rgb::Red => println!("Red"),
Rgb::Green => println!("Green"),
Rgb::Blue => println!("Blue"),
}
}
这里会打印Red,match要求匹配的值每个可能的case都要实现,硬性规定
这样写会报错
let color = Rgb::Red;
match color {
Rgb::Red => println!("Red"),
Rgb::Green => println!("Green"),
}
✅ Option 枚举匹配
业务场景: 通常用于获取内容,比如数据库查询只会有两个结果,一个是值内容,一个是无内容
#[test]
fn match_script() {
let maybe_value: Option<i32> = Some(41);
match maybe_value {
Some(x) => println!("Got a value: {}", x),
None => println!("No value"),
}
let maybe_none: Option<i32> = None;
match maybe_none {
Some(x) => println!("Got a value: {}", x),
None => println!("No value"),
}
}
output
Got a value: 41
No value
枚举匹配,会将变体的值带入,例如 Some(x) => println!("Got a value: {}", x), 匹配上就是 Some(41), => 后面的相当于函数实现,x 相当于实参
✅ Result 枚举匹配
业务场景: 通常用于结果资源,比如数据库连接只会有两个结果,一个是连接成功,一个是连接错误
use std::fs::File;
#[test]
fn match_script() {
let maybe_value: Result<&str, &str> = Ok("successful");
match maybe_value {
Ok(x) => println!("Got a value: {}", x),
Err(e) => println!("Error : {}", e),
}
// 打开文件
match File::open("Cargo.lock") {
Ok(_file) => println!("Opened file successfully"),
Err(error) => println!("Error opening file: {}", error),
}
}
output
Got a value: successful
Opened file successfully
- Result 和 Option 枚举匹配用法一样
-
_file表示这个变量暂时没用到
✅ 结构形式匹配
✅ 解构元组
#[test]
fn match_script() {
let maybe_tuple = (0, "msg", "data");
match maybe_tuple {
(0, x, "data") => {
println!("tuple.code: {:?}", x);
}
(0, "msg", "data_list") => {
println!("tuple is {:?}", maybe_tuple);
}
(0, x, y) => println!("tuple.msg: {:?} data: {:?} ", x, y),
_ => println!("tuple is not match"),
}
}
output
tuple.code: "msg"
-
match要求匹配的各种可能性都要实现,如果不想写那么多,最后加一个_通配默认实现即可 - 匹配中也支持具体值匹配,例如
(0, "msg", "data_list")和(0, x, y)都是可以匹配上的 - 匹配参数支持混合,
(0, x, "data")(z, x, y) - 最终输出的是
(0, x, "data")分支,而不是(0, x, y),优先匹配上面的代码
还是原来的代码,现在换个 case 顺序
#[test]
fn match_script() {
let maybe_tuple = (0, "msg", "data");
match maybe_tuple {
(0, x, y) => println!("tuple.msg: {:?} data: {:?} ", x, y),
(0, x, "data") => {
println!("tuple.code: {:?}", x);
}
(0, "msg", "data_list") => {
println!("tuple is {:?}", maybe_tuple);
}
_ => println!("tuple is not match"),
}
}
这种编译就通不过了,第一个 case 已经涵盖了第二个 case,代码冗余了
✅ 解构结构体
struct Point {
x: i32,
y: i32,
}
#[test]
fn match_script() {
let p = Point { x: 10, y: 10 };
match p {
Point { x: 0, y } => println!("match Point.y: {}", y),
Point { x, y } => println!("Point: ({}, {})", x, y),
}
}
output
Point: (10, 10)
这里我的Point { x, y }已经是所有可能性实现了,再加_ => println!("match _"),就会报错了
✅ 使用守卫
在 case 中添加 if 语句
match variable {
val if 条件 => {...}
}
#[test]
fn match_script() {
let num = 7;
match num {
n if n < 5 => println!("{} is less than 5", n),
n if n % 2 == 0 => println!("{} is even and >=5", n),
_ => println!("{} is odd and >=5", num),
}
}
output
7 is odd and >=5
✅ 引用匹配
#[test]
fn match_script() {
let s = String::from("hello");
match &s[..] {
"hello" => println!("Greeting!"),
_ => println!("Something else"),
}
// s 仍然可用
println!("s is still alive: {}", s);
}
output
Greeting!
s is still alive: hello
&s[..] 有两层意思
[..] 为范围索引取值,语法[a..b],
- a:起始索引(包含,默认 0);
- b:结束索引(不包含,默认值为类型的长度);
- ..:省略 start 和 end 时,代表 “从开头到结尾” 的全范围。
&s 在这里作用为 String -> &str 类型
match 匹配 String,先转化为字符切片,与 case 数据类型一致,这是常规写法
在这里demo中, match &s[..] 等价于 match &s
再来看一个示例
#[test]
fn match_script() {
let x = Some(5);
match &x {
Some(val) => println!("x is {}", val),
None => println!("None"),
}
println!("x is alive: {}", x.unwrap());
let y = Some(6);
match y {
Some(val) => println!("y is {}", val),
None => println!("None"),
}
println!("y is alive: {}", y.unwrap());
}
output
x is 5
x is alive: 5
y is 6
y is alive: 6
x y 都正常输出了,通过 x.as_ref() 借用值使用自然没问题,后续所有权依旧可以使用
但是 5 是可拷贝类型,Some(5) 是枚举啊???
这里有个规则:枚举的所有变体的所有关联数据都实现了 Copy 时,枚举才能派生 / 自动实现 Copy
Option 里只有 Some 变体关联了 T,Some(5) 也就意味着该 Option 实现了可 Copy
pub enum Option<T> {
None,
Some(T),
}
换成下面这种写法就会报错,String 不是可拷贝类型
#[test]
fn match_script() {
let x = Some(String::from("hello"));
match x {
Some(val) => println!("x is {}", val),
None => println!("None"),
}
println!("x is alive: {}", x.unwrap()); // x 的所有权已经给了 match,如果此处要运行,就需要改成 match &x
}
✅ 多条件匹配
#[test]
fn match_script() {
let day = 6;
match day {
1 | 2 | 3 | 4 | 5 => println!("Weekday"),
6 | 7 => println!("Weekend"),
_ => println!("Invalid day"),
}
}
一笔带过
✅ 返回值
#[test]
fn match_script() {
let status_code = 404;
let message = match status_code {
200 => "OK",
404 => "Not Found",
500 => "Internal Server Error",
_ => "Unknown",
};
println!("Status: {}", message);
}
output
Status: Not Found
match 不仅仅起 switch 的作用,还有返回值可以用
如果有变量接收当返回值用时,每个 case 就要有 return 了(上面例子单一字符川也是return)
let message = match status_code {
200 => "OK",
404 => println!("Not Found"),
500 => "Internal Server Error",
_ => "Unknown",
};
这种编译不过的
✅ Vec/数组匹配
#[test]
fn match_script() {
let arr = [1, 2, 8];
match arr {
[1, 2, 3] => println!("Exact match"),
[x, y, ..] => println!("First: {}, Second: {}", x, y),
}
}
output
First: 1, Second: 2
- 匹配向量和数组,长度必须是固定的,比方说你 case 里面写
[1, 2, 3, 8]就会报错 -
[x, y, ..]这种写法会通配匹配,x, y为匹配的向量前两个值,后面的..为通配作用,这个..在一个 case模式 中只允许出现 1 次,位置随意,[.., y, z][x, .., z]
✅ 写法变种
if let
if 写法式的 match
这种方式通常用于业务中只处理一种模式时,起 if 作用逻辑
#[test]
fn match_script() {
let optional_target = Some("rustlings");
if let Some(word) = optional_target {
println!("match {}", word)
}
// ......
}
output
match rustlings
等价于下面写法,相当于省去了所有模式的实现
let optional_targets = Some("rustlings");
match optional_targets {
Some(word) => println!("match {}", word),
None => {}
}
// ......
while let
while 写法式的 match
常用于重复从某个可变结构中取出值,直到不满足某种模式为止。
非常用于数组向量、元组这类复合数据类型循环遍历
#[test]
fn match_script() {
let mut optional_integers: Vec<Option<i32>> = vec![Some(3), Some(2), Some(1)];
let mut cursor = 1;
while let Some(Some(n)) = optional_integers.pop() {
assert_eq!(n, cursor);
cursor += 1;
}
println!("✅ while let works!");
}
output
✅ while let works!
- 这里本质是将 while 与 match 的语法结合起来
-
pop方法是出栈,注意哈,pop的返回值也是一个Option<T>,这里在取值时要解析两层 Option,Some(Some(n)) -
pop出完栈就没了,组合变量声明表达式(let = xxx)这种写法,形成了一个有限的 while 循环
等价于下面代码
#[test]
fn match_script() {
let mut optional_integers: Vec<Option<i32>> = vec![Some(3), Some(2), Some(1)];
let mut cursor = 1;
loop {
match optional_integers.pop() {
Some(Some(n)) => {
assert_eq!(n, cursor);
cursor += 1;
}
Some(None) => {}
None => break, // 向量所有值取完了
}
}
println!("✅ while let works!");
}