文章目录
概述
模式匹配是一种机制,用于判断某个类型或值是否匹配某一个模式,一旦匹配,就可以根据模式中指定的规则对类型或值进行解析。模式匹配经常应用于数据结构字符串中,比如在某个字符串中查找特定字串。在Rust中,模式匹配已经成为语言设计的关键特性之一,并可以应用于更加广泛的数据类型中。
模式
模式(PATTERN)是 Rust 中特殊的语法,它用来匹配类型的结构,无论类型是简单还是复杂。结合使用模式和match表达式以及其它结构可以提供更多对程序控制流的支配权。模式通常由以下一些内容组合而成:
- 字面值;
- 解构的数组、枚举、结构体或者元组;
- 变量;
- 通配符;
- 占位符。
这些部分描述了我们要处理的数据的形式,接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。
模式使用的位置
模式在Rust语言中的应用广泛,除了最常见match语句外,包括let、if let、while let、for语句以及函数参数中都有对模式的使用。
let
不同于其它语言,Rust使用let定义变量的本质也是在使用模式匹配,在Rust中,使用let语句的正式语法形式其实是下面这个样子:
let PATTERN = EXPRESSION;
考虑下面一个简单的let变量定义:
let x = 5;
这里变量名实际就是一种形式简单的模式,所以语句let x = 5;
中,x是一个模式代表“将匹配到的值绑定到变量x”。
if let
在if语句中,可以将let作为判断条件,如:
let x = Some(5);
if let Some(val) = x {
println!("val = {}", val);
}
while let
while let
允许在while循环中连续使用模式匹配,匹配失败时会退出循环,使用示例如下:
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top);
}
match
Rust的match类似于C语言中的switch,但提供了更强大的流程控制能力,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应的代码。match
的语法形式如下:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
for
在 for 循环中,模式是 for 关键字直接跟随的值,正如for x in y
中的x
。下面示例展示了在for循环中使用模式来解构元组:
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
模式的Refutability( 可反驳性)
根据模式是否会匹配失效,Rust模式可以分为两种两种形式: refutable( 可反驳的)和 irrefutable( 不可反驳的),其中:
- 能匹配任何传递的可能值的模式被称为是不可反驳的( irrefutable),如
let x = 5
,语句中的模式x
可以匹配任何值,所以不可能失败; - 对某些可能的值进行匹配会失败的模式被称为是可反驳的(refutable)。一个这样的例子便是
if let Some(x) = a_value
表达式中的 Some(x) ;如果变量a_value中的值是None而不是Some,那么Some(x) 模式不能匹配。
函数参数、 let 语句和 for 循环只能接受不可反驳的模式, 因为通过不匹配的值程序无法进行有意义的工作;if let 和 while let 表达式被限制为只能接受可反驳的模式, 因为根据定义他们意在处理可能的失败: 条件表达式的功能就是根据成功或失败执行不同的操作。
模式语法
Rust支持的模式语法类型见下表:
接下来会介绍日常开发常用的一些模式。
模式中的字面量、变量与通配符
字面量、变量等都可以作为模式在Rust中使用:
match get_count() { // get_count返回整型数值
0 => println!("res is 1."),
1 => println!("res is 1."),
count => println!("res is {}", count),
}
这里0、1等整数值都是作为模式在使用;对于变量count,可以匹配其它任意值并保存。很重要的一条规则:模式匹配的值会被复制或转移到模式内定义的局部变量(如果有)中,具体是复制还是转移则取决于匹配的值是否是可复制类型(实现了Copy trait)。
通配符_
关于通配符_,类似于C语言switch语句中default分支,用于匹配不与任何其它模式匹配的值:
match get_count() { // get_count返回整型数值
0 => println!("res is 0."),
1 => println!("res is 1."),
_ => {}, // 前面语句无法匹配,通通走这条分支进行处理
}
结构体模式
结构体模式用于匹配结构体,其中每个字段都是一个子模式:
struct Point {
x: i32,
y: i32,
}
let point = Point {x: 5, y: 6};
match point {
Point {x: x, y: y} => println!("Point at {},{}", x, y),
}
匹配结构体的每个字段都会被拷贝到新的局部变量中。对于复杂的结构体类型,如果只想匹配其中几个字段,可以使用..
省略其它字段:
match point {
Point {x: x, ..} => println!("x is {}", x),
}
引用模式
对于引用,Rust支持两种模式:ref模式和&模式,其中,ref模式匹配值并借用匹配值的引用;&模式匹配引用。下面的示例较为直观地展示了ref和&在使用上的差异:
let mut a: i32 = 1;
// ref匹配值
let ref a_ref: i32 = a; // 借用a的不可变引用
let ref mut a_mref: i32 = a; // 借用a的可变引用
// &匹配引用
let &b: &i32 = &a; // 匹配不可变引用,b为a的副本
let &mut b: &mut i32 = &mut a; // 匹配可变引用,b为a的副本
下面针对ref和&的使用更详细地进行说明。
ref模式
Rust使用模式匹配不可复制的值会转移值,考虑下面的代码:
struct Account {
name: String,
language: String,
}
match account {
Account{name, language} => {
println!("account: {}, {}", name, language);
println!("account: {}, {}", account.name, account.language); // 错误:使用转移的值account
}
}
这里,由于account.name和account.language都是不可复制的,因而匹配会导致其值被转移到局部变量name和language中,之后account也就不能再使用了。这种情况下,如果只想借用account的成员值,就可以使用ref模式。ref模式可以实现借用而不转移匹配的值。
match account {
Account{ref name, ref language} => {
println!("account: {}, {}", name, language);
println!("account: {}, {}", account.name, account.language); // account仍然可以正常使用
}
}
这里,局部变量name和language存储的是对account中对应字段的不可变引用。如果希望借用可变引用,可以使用ref mut。
&模式
以&开头的模式匹配引用,以下面代码举例说明:
struct Point {
x: i32,
y: i32,
}
Point point = Point {x: 5, y: 6};
match &point {
&Point {x, y} => println!("point: {}, {}", x, y),
}
表达式和模式天生是相反的,例如表达式(x, y)用两个值创建一个新元组,模式(x,y)则相反:它匹配元组并将其破坏后取出两个值。对&而言:表达式的&创建引用,模式中的&匹配引用。匹配引用时,不能对不可变引用采取mut操作,也不能从引用(包括mut引用)中转移出值,如果对包含不可复制字段的结构体引用进行匹配则会导致错误。
相关参考
- 《Rust程序设计》
- 《Rust程序设计语言》