模式匹配,增强版的 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..=73..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), } }outputPoint: (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: 6x 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 Foundmatch 不仅仅起 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!"); }