模式匹配
此笔记记录于Rust Course,大多数为其中的摘要,少数为笔者自己的理解
match 与 if let
match 匹配
基本例子
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}
这里我们想去匹配 dire
对应的枚举类型,因此在 match
中用三个匹配分支来完全覆盖枚举变量 Direction
的所有成员类型,有以下几点值得注意:
match
的匹配必须要穷举出所有可能,因此这里用_
来代表未列出的所有可能性match
的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同- X | Y,类似逻辑运算符
或
,代表该分支可以匹配X
也可以匹配Y
,只要满足一个即可
其实 match
跟其他语言中的 switch
非常像,_
类似于 switch
中的 default
。
使用 match 表达式赋值
match
本身也是一个表达式,因此可以用它来赋值:
enum IpAddr {
Ipv4,
Ipv6
}
fn main() {
let ip1 = IpAddr::Ipv6;
let ip_str = match ip1 {
IpAddr::Ipv4 => "127.0.0.1",
_ => "::1",
};
println!("{}", ip_str);
}
模式绑定
模式匹配的另外一个重要功能是从模式中取出绑定的值,例如:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 25 美分硬币
}
接下来,我们希望在模式匹配中,获取到 25 美分硬币上刻印的州的名称:
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
上面代码中,在匹配 Coin::Quarter(state)
模式时,我们把它内部存储的值绑定到了 state
变量上,因此 state
变量就是对应的 UsState
枚举类型。
例如有一个印了阿拉斯加州标记的 25 分硬币:Coin::Quarter(UsState::Alaska)
, 它在匹配时,state
变量将被绑定 UsState::Alaska
的枚举值。
if let 匹配
有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 match
来处理就要写成下面这样:
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}
用if let
的方式实现:
if let Some(3) = v {
println!("three");
}
matches!宏
Rust 标准库中提供了一个非常实用的宏:matches!
,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true
or false
。
例如,有一个动态数组,里面存有以下枚举:
enum MyEnum {
Foo,
Bar
}
fn main() {
let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];
}
现在如果想对 v
进行过滤,只保留类型是 MyEnum::Foo
的元素,你可能想这么写:
v.iter().filter(|x| x == MyEnum::Foo);
但是,实际上这行代码会报错,因为你无法将 x
直接跟一个枚举成员进行比较。好在,你可以使用 match
来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用 matches!
:
v.iter().filter(|x| matches!(x, MyEnum::Foo));
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
变量遮蔽
无论是 match
还是 if let
,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽
模式适用场景
while let 条件循环
一个与 if let
类似的结构是 while let
条件循环,它允许只要模式匹配就一直进行 while
循环。下面展示了一个使用 while let
的例子:
// Vec 是动态数组
let mut stack = Vec::new();
// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);
// stack.pop 从数组尾部弹出元素
while let Some(top) = stack.pop() {
println!("{}", top);
}
这个例子会打印出 3
、2
接着是 1
。pop
方法取出动态数组的最后一个元素并返回 Some(value)
,如果动态数组是空的,将返回 None
,对于 while
来说,只要 pop
返回 Some
就会一直不停的循环。一旦其返回 None
,while
循环停止。我们可以使用 while let
来弹出栈中的每一个元素。
for 循环
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
这里使用 enumerate
方法产生一个迭代器,该迭代器每次迭代会返回一个 (索引,值)
形式的元组,然后用 (index,value)
来匹配。
let 语句
是的, 该语句我们已经用了无数次了,它也是一种模式匹配:
let x = 5;
这其中,x
也是一种模式绑定,代表将匹配的值绑定到变量 x 上。因此,在 Rust 中,变量名也是一种模式,只不过它比较朴素很不起眼罢了。
函数参数
函数参数也是模式:
fn foo(x: i32) {
// 代码
}
其中 x
就是一个模式,你还可以在参数中匹配元组
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
&(3, 5)
会匹配模式 &(x, y)
,因此 x
得到了 3
,y
得到了 5
。
let 与 if let
对于以下代码,编译器会报错:
let Some(x) = some_option_value;
因为右边的值可能不为 Some
,而是 None
,这种时候就不能进行匹配,也就是上面的代码遗漏了 None
的匹配。
类似 let
, for
和match
都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )。
但是对于 if let
,就可以这样使用:
if let Some(x) = some_option_value {
println!("{}", x);
}
因为 if let
允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。
全模式列表
匹配字面值
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
匹配命名变量
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y), // 引入了一个新变量 y,它会匹配任何 Some 中的值
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
}
单分支多模式
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
通过序列..=
匹配值的范围
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
解构相关
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {}", x),
Point { x: 0, y } => println!("On the y axis at {}", y),
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
}
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
)
}
}
}
struct Point {
x: i32,
y: i32,
}
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
// 定长数组
let arr: [u16; 2] = [114, 514];
let [x, y] = arr;
assert_eq!(x, 114);
assert_eq!(y, 514);
// 不定长数组
let arr: &[u16] = &[114, 514];
if let [x, ..] = arr {
assert_eq!(x, &114);
}
if let &[.., y] = arr {
assert_eq!(y, 514);
}
let arr: &[u16] = &[];
assert!(matches!(arr, [..]));
assert!(!matches!(arr, [x, ..]));
忽略模式中的值
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
//不关心里面的值,只关心元组中两个元素的类型,因此对于 Some 中的值,直接进行忽略。
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
},
}
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),
}
匹配守卫
匹配守卫(match guard)是一个位于 match
分支模式之后的额外 if
条件,它能为分支模式提供更进一步的匹配条件。
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
@绑定
@
运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 Message::Hello
的 id
字段是否位于 3..=7
范围内,同时也希望能将其值绑定到 id_variable
变量中以便此分支中相关的代码可以使用它。
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
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)
},
}
使用 @
还可以在绑定新变量的同时,对目标进行解构:
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
// 绑定新变量 `p`,同时对 `Point` 进行解构
let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
println!("x: {}, y: {}", px, py);
println!("{:?}", p);
let point = Point {x: 10, y: 5};
if let p @ Point {x: 10, y} = point {
println!("x is 10 and y is {} in {:?}", y, p);
} else {
println!("x was not 10 :(");
}
}