在Rust中,模式匹配是一种强大的编程工具,它允许你根据数据的结构来选择不同的执行路径。模式可以用在 match
表达式、if let
表达式、while let
表达式、函数参数、let
语句等地方
一个示例
来看个上一篇文章 Rust 枚举 简单入门 中例子
代码语言:javascript复制enum Color {
Red,
Orange,
Yellow
}
let c1 = Color::Red;
match c1 {
Color::Red => println!("Red"),
Color::Orange => println!("Orange"),
Color::Yellow => println!("Yellow")
}
match
会执行模式匹配,在此示例中,模式就是出现在 =>
符号前面的部分,模式匹配可以和枚举协同工作,甚至可以测试它们包含的数据
模式类型
上面的例子是匹配枚举值的模式。模式的类型不止于此,Rust 模式还有它们自己的小型语言,如下表
模式类型 | 例子 | 注意事项 |
---|---|---|
字面量 | 100 "name" | 匹配一个确切的值;也允许匹配常量名称 |
范围 | 0 ..= 100 'a' ..= 'k' 256.. | 匹配范围内的任何值,包括可能给定的结束值 |
通配符 | _ | 匹配任何值并忽略它 |
变量 | name mut count | 类似于 _,但会把值移动或复制到新的局部变量中 |
引用变量 | ref field ref mut field | 借用对匹配值的引用,而不是移动或复制它 |
与子模式绑定 | val @ 0 ..= 99 ref circle @ Shape::Circle { .. } | 使用 @ 左边的变量名,匹配其右边的模式 |
枚举型模式 | Some(value) None Pet::Orca | |
元组型模式 | (key, value) (r, g, b) | |
数组型模式 | [a, b, c, d, e, f, g] [heading, carom, correction] | |
切片型模式 | [first, second] [first, _, third] [first, .., nth] [ ] | |
结构体型模式 | Color(r, g, b) Point { x, y } Card { suit: Clubs, rank: n } Account { id, name, .. } | |
引用 | &value &(k, v) | 仅匹配引用值 |
或多个模式 | 'a' 竖线 'A' Some("left" 竖线 "right") | |
守卫表达式 | x if x * x <= r2 | 只用在 match 表达式中(不能用在 let 语句等处) |
注意! 由于当前页面的 Markdown 格式转换问题,竖线
|
会导致排版异常,因此上面表格使用中文竖线
代替|
字面量、变量、通配符
字面量可以是整数、浮点数、字符、字符串、布尔值等。下面是整数字面量的一个例子
代码语言:javascript复制let x = 5;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("something else"),
}
用单个下划线 _
作为模式,这就是通配符模式,这里的通配符模式能匹配任意值,但不会将其存储到任何地方
即使你非常确定其他情况不会发生,也必须至少添加一个后备分支,也许是 panic 的分支
代码语言:javascript复制let s = "hello";
match s {
"hello" => println!("found hello"),
other => println!("no match"),
}
这里面的 other
是一个变量名,它可以匹配任何值,匹配的值会移动或复制到一个新的局部变量中,这些模式类似 switch
语句中的 default
分支,用于匹配与任何其他模式都无法匹配的值
元组型和结构体型
元组模式是一种模式,用于匹配元组的结构。元组模式由一对圆括号和一组模式组成,模式之间用逗号分隔
代码语言:javascript复制let x = (1, 2, 3);
match x {
(1, 2, 3) => println!("one, two, three"),
(a, b, c) => println!("({}, {}, {})", a, b, c),
}
结构体模式用于匹配结构体的结构。结构体模式由结构体的名称和一组模式组成,模式之间用逗号分隔
代码语言:javascript复制struct Point {
x: i32,
y: i32,
}
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("x is {}, y is on the x axis", x),
Point { x: 0, y } => println!("x is on the y axis, y is {}", y),
Point { x, y } => println!("x is {}, y is {}", x, y),
}
当想要匹配一个大型结构体的一部分字段,而不是全部字段时,可以使用 ..
来表示剩余的字段。这被称为结构体模式的 ..
简写
struct Point {
x: i32,
y: i32,
z: i32,
// ... 其他字段
}
let p = Point { x: 0, y: 7, z: 0 };
match p {
Point { x, .. } => println!("x is {}", x),
}
match
表达式只关心 p
的 x
字段,而不关心 y
和 z
字段。..
表示剩余的字段,所以Point { x, ..
}匹配任何 Point
结构体,只要它的 x
字段匹配
数组型和切片型
数组型模式匹配数组。数组型模式通常用于过滤一些特殊情况的值,并且在处理那些不同位置的值具有不同含义的数组时也非常有用
代码语言:javascript复制let arr = [1, 2, 3];
match arr {
[1, 2, 3] => println!("found 1, 2, 3"),
_ => println!("no match"),
}
注意! 数组模式只能用于固定大小的数组,不能用于动态大小的数组(也就是切片)。如果你想要匹配一个切片的结构,你应该使用切片模式
切片型模式与数组型相似,但与数组不同,切片具有可变长度,因此切片型模式不仅匹配值,还匹配长度。..
在切片型模式中能匹配任意数量的元素
let arr = ["a"];
match &arr[..] {
[] => println!("no body"),
[a] => println!("a"),
[a, b] => println!("a, b"),
_ => println!("no match"),
}
引用型模式
引用型模式(Reference Patterns)允许你通过引用来匹配和解构数据,而不是通过值。这种模式在处理借用的数据时特别有用,因为它允许你在不获取所有权的情况下访问数据的部分或全部内容
基本用法
引用型模式通常与&
符号一起使用,表示你正在匹配一个引用。当你想要在模式匹配中解构一个引用指向的值时,这非常有用,下面是个简单的例子
let reference = &10;
match reference {
&val => println!("val: {:?}", val)
}
reference
是一个指向10
的引用。在match
表达式中,模式&val
用于解构reference
,允许直接访问它指向的值10
解构数据
引用型模式在解构复杂数据结构时尤其有用,比如元组或结构体
代码语言:javascript复制let tuple = &(1, 2, 3);
match tuple {
&(x, y, z) => println!("Matched: {}, {}, {}", x, y, z),
}
使用ref关键字
ref
关键字用于创建一个引用指向模式匹配中的值,而不是通过值绑定
let value = 5;
let ref reference = value;
match reference {
&val => println!("val: {:?}", val),
}
ref和mut结合使用
ref mut可以用来匹配可变引用,并允许修改通过引用访问的数据
代码语言:javascript复制
let mut value = 5;
match value {
ref mut r => {
*r = 10;
println!("value: {}", r);
}
}
注意!
- 使用ref mut时,必须确保被引用的数据本身是可变的
- 修改通过ref mut创建的引用所指向的数据时,需要使用解引用操作符*
- 在模式匹配中使用ref和ref mut可以让你更灵活地处理数据,特别是在需要引用而不是所有权的场景中
匹配守卫
匹配守卫(match guards)是一种与模式匹配结合使用的条件表达式,它提供了额外的条件来决定是否应该选择某个分支。这使得模式匹配更加灵活,允许在模式本身无法表达的复杂情况下进行精细的控制
匹配守卫紧跟在模式之后,使用if关键字引入,如下例子
代码语言:javascript复制let tuple = (5, 12);
match tuple {
(5, y) if y > 10 => println!("第一个元素是5,且第二个元素大于10,y = {}", y),
_ => println!("不匹配"),
}
在循环中使用匹配守卫
代码语言:javascript复制let numbers = vec![Some(10), Some(11), Some(20), Some(30)];
for option in numbers.iter() {
match option {
Some(x) if *x > 10 => println!("大于10的数字为:{}", x),
_ => (),
}
}
匹配多种可能性
模式匹配(Pattern Matching)是一种强大的控制流工具,它不仅可以匹配单一的值,还可以同时匹配多种可能性。这通过使用|
运算符来实现,|
在这里表示“或”(or),允许在同一个match
分支中指定多个模式
let pair = (2, 3);
match pair {
(x, y) if x == y => println!("数字一样"),
(x, 0) | (0, x) => println!("x: {}", x),
_ => println!("没有匹配")
}
使用@模式绑定
@
模式绑定的基本语法是在模式中使用@
后跟一个变量名,这样可以在模式匹配成功时,将匹配到的值绑定到这个变量
enum Message {
Move { x: i32, y: i32 },
ChangeColor(i32, i32, i32)
}
let move_message = Message::Move { x: 3, y: 104 };
match move_message {
Message::Move { x: x_pos @ 0..=100, y: y_pos @ 101..=200 } => {
println!("移出({}, {})", x_pos, y_pos)
},
_ => println!("other")
}
模式能用在哪里
尽管模式在 match
表达式中作用最为突出,但它们也可以出现在其他一些地方,通常用于代替标识符。但无论出现在哪里,其含义都是一样的:Rust 不是要将值存储到单个变量中,而是使用模式匹配来拆分值
// 把结构体解包成3个局部变量……
let Track { album, track_number, title, .. } = song;
// ……解包某个作为函数参数传入的元组
fn distance_to((x, y): (f64, f64)) -> f64 { ... }
// ……迭代某个HashMap上的键和值
for (id, document) in &cache_map {
println!("Document #{}: {}", id, document.title);
}
上述示例中的每一个都节省了两三行样板代码。同样的概念也存在于其他一些语言中:JavaScript 中叫作解构,而 Python 中叫作解包
看到这里,有没有越看越顺的感觉?