前言
在前面的文章 第N次入门Rust - 5.枚举和模式匹配 中,已简单介绍了模式匹配的一些简单用法。这一篇将继续讨论模式匹配在Rust中的使用,有一部分提到的语法还没用过,先记下来~
15.1 模式匹配简介
模式匹配(Pattern Match)通常指的是通过特殊的语法或者规则从一堆数据中心找出符合规则的目标数据。
在Rust中模式是Rust中的一种特殊语法,用于匹配复杂和简单类型的结构。
在前面的文章中最常用模式匹配的地方是match表达式,通过匹配值的类型执行不同的分支逻辑,这在Java(这里指Java1.8,后续版本暂时没了解,毕竟版本任你发,我用Java8 @の@)中是不容易实现的。Java8中的match表达式只能根据取值来选择分支逻辑,要实现基于类型选择分支,只能借助instanceof
运算符,且具体实现效果没有Rust那么强大。
模式通常由以下项组成:
- 字面值
- 解构的数组、枚举、结构体或者元组
- 变量
- 通配符
- 占位符
在下列的说明中:
VALUE
是参与模式匹配的值;PATTERN
可以是下列的语法:pattern
:只匹配模式pattern
,不提取值VALUE
中的信息;pattern(val)
:当值VALUE
匹配模式pattern
,就将VALUE
中的信息提取出来放入val
中,且在之后的上下文中可以使用val
。注意,这个pattern
除了可以是一般类型以外,也可以是结构体或元组。如果是结构体或元组,则可以精确到每个位置或者某些位置的字段是什么类型或者取值,并提取出某些字段的取值。
EXPRESSION
是匹配成功后执行的表达式;
Rust模式匹配流程伪代码如下:
def match_and_get(PATTERN, VALUE):
if PATTERN is pattern形式语法:
if VALUE 匹配 PATTERN模式:
return (true, None)
else:
return (false, None)
else if PATTERN is pattern(val)形式语法:
if VALUE 匹配 PATTERN模式:
val = 从VALUE中获取到的值
return (true, val)
else:
return (false, None)
def execute(PATTERN, VALUE, EXPRESSION):
is_match, val = match_and_get(PATTERN, VALUE)
if is_match:
EXPRESSION(val)
return true
else:
return false
15.2 使用模式匹配的场景
15.2.1 match
表达式
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
_ => EXPRESSION,
}
match
表达式必须是 **穷尽(exhaustive)**的,意为match
表达式所有可能的值都必须被考虑到。- 一个特殊的模式:
_
(下划线),会匹配任何东西:- 它不会绑定任何变量;
- 通常用于
match
的最后一个arm,或用于忽略某些值;
15.2.2 if let
条件表达式
if let
表达式等同于只关心一种情况的match
语句的简写形式。if let
可以对应一个可选的带有代码的else
在if let
中的模式不匹配时运行。
if let PATTERN = VALUE {
/* ... */
} else {
/* ... */
}
15.2.3 while let
条件循环
while let PATTERN = VALUE {
/* ... */
}
- 只要
VALUE
满足模式PATTERN
,就一直执行循环。 - 通常会将
VALUE
中的值提取出来到val
中,在循环体内使用。
15.1.4 for
循环
for PATTERN in VALUE {
/* ... */
}
15.1.5 let
语句
let
语句也可以使用模式匹配。
let PATTERN = EXPRESSION;
例子:
let x = 10;
let (x, y, z) = (1, 2, 3);
let Point{x, y} = Point::new(10, 20);
15.1.6 函数参数
fn foo(PATTERN) {
/* ... */
}
foo(VALUE)
例子:
// x实际上就是一个模式
fn foo(x: i32) { /* ... */ }
fn foo(&(x, y): &(i32, i32)) { /* ... */ }
foo(&(3, 5))
15.2 可反驳性模式和不可反驳性模式
- 模式有两种形式:
refutable
(可反驳的)和irrefutable
(不可反驳的)。 - 可反驳的模式:代码逻辑允许模式不匹配,如果模式不匹配则不执行相关逻辑。 可反驳的模式匹配通常涉及条件。检查一个已有的值是否匹配模式,如果匹配,则直接执行一段逻辑,或者先从中抽取想要的值,然后执行一段逻辑。
- 不可反驳的模式:代码逻辑必须匹配模式,如果模式不匹配则编译错误。 不可反驳的模式通常这涉及赋值,即从一个已有的值中抽取出想要的值。
用到模式的地方 | 类型 |
---|---|
if let | 可反驳的模式 |
while let | 可反驳的模式 |
match | 不可反驳的模式 |
函数参数 | 不可反驳的模式 |
let 语句 | 不可反驳的模式 |
for 循环 | 不可反驳的模式 |
15.3 所有模式语法
15.3.1 匹配字面值
匹配字面量的意思是模式本身是一个字面量,这种语法类似于Java的Switch,起到的是分支作用。
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
15.3.2 匹配命名变量
匹配命名变量,是不可反驳模式。在值匹配模式后,将值复制到另一个新的变量上,这个变量只能在匹配成功的逻辑分支中使用。
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"), // 匹配字面量
Some(y) => println!("Matched, y = {:?}", y), // 匹配命名变量,y的作用域是match内
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
}
15.3.3 匹配多个模式
- 与正则表达式的匹配多个模式中的一个类似,通常用于多个模式对应同一个逻辑分支。
- 使用
|
语法匹配多个模式,or
的意思。
let x = 1;
match x {
1 | 2 => println!("one or two"), // 匹配1或者2
3 => println!("three"),
_ => println!("anything"),
}
15.3.4 匹配区间范围内的值
- 使用双目运算符
..=
。 - 模式
a..=b
意思为数学上的左闭右闭区间[a,b]
。 - 也等价于
a | a+1 | a+2 | ... | b
。 - 运算符接受的类型除了数字还可以是char值。
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
15.3.5 解构
- 解构含义为从匹配模式的给定值中提取出提取出对应的字段值。
- 解构分为两步:
- 模式匹配
- 提取字段值
- 可以解构的对象:结构体、枚举、元组、引用。
解构结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p; // a = 0; b = 7;
let Point { x: 0, y: c} = p; // c = 7;
let Point { x: d, y: 7} = p; // d = 0;
}
解构枚举
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
)
}
}
}
解构嵌套的结构体和枚举
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(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
)
}
_ => ()
}
}
解构结构体和元组
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
15.3.5 匹配时忽略模式中的值
使用 _
忽略部分值或者整个值
下划线_
用于模式中时,作用为忽略给定值中对应位置的值。
let a = (1, 2);
// 忽略部分值(第一个字段的值)
if let (_, x) = a {
/* ... */
}
// 忽略整个值(第一个参数的值),虽然这种语法不太推荐使用,建议把第一个参数删掉
fn run(_: i32, b: i32) {
/* ... */
}
let x = 10;
match x {
10 => /* ... */
_ = > /* 非10时的逻辑 */
}
通过在名字前以一个下划线开头来忽略未使用的变量
- 通常存在未使用变量编译器会给出一个警告,认为这里是一个bug,但是有时创建一个还未使用的变量是有用的,比如会在后续才用到这个值等,这是可以在变量名前加一个下划线
_
可以忽略掉未使用警告,但是变量还是会正常声明。 - 使用
_
和_变量名
两种语法是不一样的:前者值没有被绑定,后者还是会发生值的绑定。
fn main() {
let _x = 5;
let y = 10;
}
用 ..
忽略剩余值
-
..
忽略剩余值的语法可以看成是_
忽略值的一个语法糖,通常使用_
忽略模式中的值往往是需要忽略的值比较少的使用才会用到,当需要忽略的值非常多,想要提取的值比较少时,使用..
忽略会更方便。 -
只提取结构体中少数字段的值:
struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), }
-
只提取元组中首尾的值:
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {}, {}", first, last); }, } }
-
注意:使用
..
忽略剩余值必须没有歧义,下面的例子就是有歧义的,无法通过编译:fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (.., second, ..) => { println!("Some numbers: {}", second) }, } }
15.3.7 匹配守卫提供条件判断
- 匹配守卫(match guard) 是一个指定于 match 分支模式之后的额外
if
条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。 - 带有匹配守卫的模式匹配逻辑如下:
- 匹配给定值是否满足模式A;
- 如果不满足模式A,开始匹配下一个模式;
- 如果满足模式A,从给定值的提取出值
val
; - 执行匹配守卫的条件判断语句,其中
val
可以在判断语句中使用; - 如果没有通过匹配守卫的条件判断,开始匹配下一个模式;
- 如果通过匹配守卫的条件判断,进入对应的分支,分支内
val
可以被使用;
- 语法:
match VALUE { PATTERN1 if cond1 => /* ... */ PATTERN2 if cond2 => /* ... */ PATTERN3 => /* ... */ _ => /* ... */ }
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
15.3.8 at 运算符(@
) 绑定
- at 运算符(
@
)允许开发者在创建一个存放值的变量的同时测试其值是否匹配模式。
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
// 匹配结构体,并将id字段的值提取出来到id_variable中,然后判断是否属于[3,7]
Message::Hello { id: id_variable @ 3..=7 } => {
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)
},
}