十四、模式与匹配
Published on
模式是Rust的一种特殊语法,用于对类型的结构进行匹配。一个模式由若干以下项组成:
- 字面值
- 解构的数组、枚举、结构体或元组
- 变量
- 通配符
- 占位符
在模式有效的范围内,它描述了数据的形状。程序将值与模式进行匹配,以确定它是否有正确的形状,并决定是否进一步执行一段代码。
可以使用模式的位置
match分支
在match中使用模式要求穷举所有可能性,一种保证穷举的方法是在最后一个分支处理所有剩余所有可能性。使用_可以匹配任何可能性并且不绑定值,所以一般用在最后一个分支。
match x {
None => None,
Some(i) => Some(i + 1),
}
if-let条件表达式
if-let/else-if/else-if-let可以混用。使用if-let的缺点是如果不在最后使用else,则不能穷举所有情况。
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
while-let条件循环
while let Some(top) = stack.pop() {
println!("{}", top);
}
for循环
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
let语句
// 模式与值匹配,进行变量绑定
let (x, y, z) = (1, 2, 3);
// 匹配失败
let (x, y) = (1, 2, 3);
函数参数
函数(或闭包)的参数实际上也是模式。
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
可否认性
模式分为可否认与不可否认两类,可匹配所有可能性的模式为不可否认模式,可能匹配失败的模式为可否认模式。
// 不可否认,x可匹配任何值
let x = 5;
// 可否认,a_value为None时失败
if let Some(x) = a_value
函数参数、let语句和for循环只接受不可否认模式,if-let和while-let表达式接受可否认与不可否认模式。
- 在需要使用不可否认模式时,如果使用可否认模式,编译器会报错,因为未处理所有情况。如果必须使用,可改为if-let语句
- 在需要使用可否认模式时,如果使用不可否认模式,编译器会给出警告,因为条件语句的目的就是处理可能失败的情况
- 在match语句中,除最后一个分支外,应使用可否认模式,最后一个分支应使用不可否认模式以处理剩余所有情况
- match语句可以只有一个使用不可否认模式的分支,但是使用let语句更加简洁
模式语法
匹配字面值
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
匹配命名变量
命名变量是可以匹配任何值的不可否认模式。由于match开始了一个新块,所以match语句中的变量是新的变量。
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"),
_ => println!("Default case, x = {:?}", x),
}
匹配多个模式
在match语句中,可以使用|(模式或操作符)匹配多个模式。
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
匹配范围
可以使用..=语法匹配一个范围内的值。只能用在数值类型与字符类型上。
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
解构
解构元组
let triple = (0, -2, 3);
match triple {
// 解构第2个和第3个元素
(0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
// `..`表示忽略其它值
(1, ..) => println!("First is `1` and the rest doesn't matter"),
(.., 2) => println!("last is `2` and the rest doesn't matter"),
(3, .., 4) => println!("First is `3`, last is `4`, and the rest doesn't matter"),
// `_`表示其它所有情况
_ => println!("It doesn't matter what they are"),
}
解构数组/切片
let array = [1, -2, 6];
match array {
// 绑定第2个和第3个元素
[0, second, third] =>
println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third),
// 使用_忽略一个元素
[1, _, third] => println!(
"array[0] = 1, array[2] = {} and array[1] was ignored",
third
),
// 绑定一些值,忽略其它值
[-1, second, ..] => println!(
"array[0] = -1, array[1] = {} and all the other ones were ignored",
second
),
// 将剩余值保存在另一个数组或切片中
[3, second, tail @ ..] => println!(
"array[0] = 3, array[1] = {} and the other elements were {:?}",
second, tail
),
// 多种模式组合
[first, middle @ .., last] => println!(
"array[0] = {}, middle = {:?}, array[2] = {}",
first, middle, last
),
}
解构指针/引用
对于指针,解构与解引是两种不同的操作:
- 解引使用*
- 解构使用&,ref与ref mut
// 解构引用,使用&
match reference {
&val => println!("Got a value via destructuring: {:?}", val),
}
// 先解引再解构
match *reference {
val => println!("Got a value via dereferencing: {:?}", val),
}
// 使用ref
match value {
ref r => println!("Got a reference to a value: {:?}", r),
}
// 使用ref mut
match mut_value {
ref mut m => {
// 解引后使用
*m += 10;
println!("We added 10. `mut_value`: {:?}", m);
},
}
解构结构体
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 0, y: 7 };
// 将x,y解构至变量a,b
let Point { x: a, y: b } = p;
// 简化语法,相当于:let Point { x: x, y: y } = p;
let Point { x, y } = p;
// 使用字面值用于条件判断
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),
}
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 {x} and in the y direction {y}");
}
// 含有单个数据的分支
Message::Write(text) => {
println!("Text message: {text}");
}
// 含有类元组数据的分支
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {r}, green {g}, and blue {b}",)
}
}
解构嵌套结构体和枚举
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}")
}
_ => (),
}
解构嵌套结构体和元组
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
在模式中忽略值
使用_忽略一个值
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
使用嵌套_忽略部分值
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
忽略使用_开始的变量
let _x = 5;
// 虽然_s没有使用,但是变量s仍然被移动至_s
if let Some(_s) = s {
println!("found a string");
}
// 没有进行绑定,s没有被移动
if let Some(_) = s {
println!("found a string");
}
使用..忽略剩余值
match origin {
Point { x, .. } => println!("x is {}", x),
}
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
使用match guards添加额外条件
match guard是在match分支后面的if条件,必须同时满足该条件才能执行该分支。需要注意编译器在检查是否覆盖所有模式时并不会考虑match guard。
match num {
Some(x) if x % 2 == 0 => println!("The number {} is even", x),
Some(x) => println!("The number {} is odd", x),
None => (),
}
使用@进行绑定
一些情况下分支无法直接使用变量值,需要使用@进行绑定。
match age() {
0 => println!("I haven't celebrated my first birthday yet"),
// 不绑定则无法获得age的值
n @ 1 ..= 12 => println!("I'm a child of age {:?}", n),
n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n),
n => println!("I'm an old person of age {:?}", n),
}