Rust 学习笔记:模式匹配
Rust 学习笔记:模式匹配
本文我们将讨论使用模式的有效位置、可反驳模式和不可反驳模式之间的区别,以及不同类型的模式语法。
模式
模式是 Rust 中的一种特殊语法,用于匹配复杂和简单类型的结构。将模式与匹配表达式和其他结构结合使用,可以更好地控制程序的控制流。
模式由以下内容的组合组成:
- 字面量:例如数字、字符串
- 解构数据结构:数组、枚举、结构体、元组,等等
- 变量
- 通配符:_ 表示任意值
- 占位符:尚未具体定义的部分
为了使用模式,我们将其与某个值进行比较。如果模式与值匹配,则在代码中提取并使用数据。
可以使用模式的地方
match 分支
形式上,匹配表达式定义为关键字 match,要匹配的值,一个或多个匹配分支,由一个模式和一个表达式组成,如果值匹配该模式,则运行该表达式。
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
匹配表达式的一个要求是它们必须是详尽的,即必须考虑到匹配表达式中值的所有可能性。确保涵盖了所有可能性的一种方法是为最后一个分支提供一个全面的模式:例如,匹配任何值的变量名永远不会失败,因此涵盖了所有剩余的情况。
通配符 _ 将匹配任何东西,但它从不绑定到变量,因此它通常用于最后一个匹配分支。例如,当您想忽略任何未指定的值时,_ 模式可能很有用。
if let 条件表达式
if let 表达式主要用作编写只匹配一种情况的等效匹配的简短方式。可选地,if le t可以有一个相应的 else,其中包含在 if let 中的模式不匹配时运行的代码。
也可以混合匹配 if let、else if 和 else if le t表达式。与匹配表达式相比,这样做提供了更大的灵活性,在匹配表达式中,我们只能表达一个值来与模式进行比较。同样,Rust 也不要求一系列 if let, else if, else if let 中的条件相互关联。
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
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");
}
}
如果用户指定了喜欢的颜色,该颜色将被用作背景。如果没有指定喜欢的颜色,并且今天是星期二,则背景色为绿色。否则,如果用户将他们的年龄指定为字符串,并且我们可以成功地将其解析为数字,则颜色是紫色或橙色,具体取决于数字的值。如果这些条件都不适用,则背景色为蓝色。
if let 还可以引入新变量,以与匹配分支相同的方式遮蔽现有变量:if let Ok(age) = age 这行引入了一个新的 age 变量,该变量包含 Ok 变量中的值,从而遮蔽现有的 age 变量。这意味着我们需要将 if age > 30 条件放在该块中:我们不能将这两个条件合并为 if let Ok(age) = age && age > 30。要与 30 进行比较的新 age 在以花括号开始的新作用域之前是无效的。
使用 if let 表达式的缺点是编译器不检查穷尽性,而使用 match 表达式则检查穷尽性。如果我们省略了最后一个 else 块,从而错过了处理某些情况,编译器将不会提醒我们可能存在的逻辑错误。
while let 条件循环
与 if let 的构造类似,while let 条件循环允许 while 循环运行,只要模式继续匹配。
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
recv 方法从通道的接收端获取一条消息,并返回一个 Ok(value)。只要发送方存在,每次消息到达时,recv 方法都会返回 Ok,然后在发送方断开连接时产生 Err,退出 while 循环。
for 循环
在 for 循环中,直接跟在关键字 for 后面的值是一个模式。例如,对于 for x in y,x 是模式。
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
我们使用 enumerate 方法产生迭代器,使其产生一个值和该值的索引,并将其放入元组中。生成的第一个值是元组 (0, ‘a’)。当此值与模式 (index, value) 匹配时,index 将为 0,value 将为 ‘a’,打印输出的第一行。
let 语句
let 语句实际上也使用了模式。更正式地说,let 语句是这样的:
let PATTERN = EXPRESSION;
变量名只是模式的一种特别简单的形式。Rust 将表达式与模式进行比较,并分配它找到的任何名称。
例如,在 let x = 5;
中,x 是一个模式,表示“将匹配的内容绑定到变量 x”。因为名称 x 是整个模式,因此该模式实际上意味着“将所有内容绑定到变量 x,无论值是多少”。
要更清楚地了解 let 的模式匹配方面,请考虑下面的的代码,它使用 let 的模式来解构元组。
let (x, y, z) = (1, 2, 3);
这里,我们将一个元组与一个模式进行匹配。Rust 将值 (1, 2, 3) 与模式 (x, y, z) 进行比较,并看到值与模式匹配,因为两者中的元素数量相同,因此 Rust 将 1 绑定到 x, 2 绑定到 y, 3 绑定到 z。你可以将此元组模式视为嵌套三个独立变量模式。
如果模式中的元素数量与元组中的元素数量不匹配,则整体类型将不匹配,我们将得到编译器错误。
要修复此错误,可以使用 _ 或 … 忽略元组中的一个或多个值。
函数参数
函数参数也可以是模式。
fn foo(x: i32) {
// code goes here
}
例如,foo 函数的参数 x 部分就是一个模式。
与 let 一样,可以将函数参数中的元组与模式进行匹配。
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 x = 5;
中的x,因为 x 匹配任何东西,所以不能不匹配。
无法匹配某些可能值的模式是可反驳的。一个例子是表达式 if let Some(x) = a_value
中的 Some(x),因为如果 a_value 变量中的值是 None 而不是 Some,则 Some(x)模 式将不匹配。
函数参数、let 语句和 for 循环只能接受不可反驳的模式,因为当值不匹配时,程序不能做任何有意义的事情。
if let 和 while let 表达式和 let…else 语句接受可反驳的和不可反驳的模式,但是编译器警告不可反驳的模式,因为根据定义,它们旨在处理可能的失败:条件语句的功能在于它根据成功或失败执行不同的能力。
一般来说,你不必担心可反驳模式和不可反驳模式之间的区别,但是需要熟悉可反驳性的概念,以便在错误消息中看到它时能够做出响应。在这些情况下,你需要根据代码的预期行为,更改模式或使用模式的结构。
当我们尝试使用一个不可反驳的模式时,Rust 需要一个不可反驳的模式,反之亦然。
let Some(x) = some_option_value;
如果 some_option_value 是 None,它将无法匹配模式 Some(x),这意味着该模式是可反驳的。然而,let 语句只能接受一个不可反驳的模式,因为代码无法对 None 执行任何有效操作。因为我们没有使用模式 Some(x) 覆盖(也不可能覆盖)每个有效值,Rust 产生一个编译器错误。
我们可以通过修改使用该模式的代码来修复它:使用 if let 而不是 let。如果模式不匹配,代码将跳过花括号中的代码,从而使其能够有效地继续。
let Some(x) = some_option_value else {
return;
};
但是,如果给 if let 一个不可反驳的模式(总是匹配的模式),编译器将给出警告。
let x = 5 else {
return;
};
Rust 指出:在不可辩驳的模式中使用 if let 是没有意义的。
由于这个原因,匹配分支必须使用可反驳的模式,但最后一个分支除外,它应该用不可反驳的模式匹配任何剩余的值。
Rust 允许我们在只有一个分支的匹配中使用无可反驳的模式,但是这种语法不是特别有用,可以用更简单的 let 语句代替。
现在你已经知道了在哪里使用模式,以及可反驳模式和不可反驳模式之间的区别,接下来让我们讨论可用于创建模式的所有语法。