所有模式可以应用的地方

模式在 Rust 中很多地方都会出现,而你一直在使用它们,只是没有意识到!本节讨论模式有效的所有地方。

match 分支

正如第 6 章所讨论的,我们在 match 表达式的分支中使用模式。形式上,match 表达式被定义为关键字 match,一个要匹配的值,以及一个或多个匹配分支,这些分支由一个模式和一个表达式组成,如果值与该分支的模式匹配,则运行该表达式,像这样

match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, PATTERN => EXPRESSION, }

例如,这是来自列表 6-5 的 match 表达式,它匹配变量 x 中的 Option<i32>

match x { None => None, Some(i) => Some(i + 1), }

这个 match 表达式中的模式是每个箭头左侧的 NoneSome(i)

match 表达式的一个要求是它们需要是穷尽的,即 match 表达式中值的所有可能性都必须被考虑到。确保你涵盖了每种可能性的一种方法是为最后一个分支设置一个捕获所有模式:例如,匹配任何值的变量名永远不会失败,因此涵盖了所有剩余情况。

特定的模式 _ 将匹配任何内容,但它永远不会绑定到变量,因此它经常在最后一个匹配分支中使用。例如,当你想忽略任何未指定的值时,_ 模式会很有用。我们将在本章后面的 “在模式中忽略值”节中更详细地介绍 _ 模式。

条件 if let 表达式

在第 6 章中,我们讨论了如何使用 if let 表达式,主要是作为编写等效于只匹配一种情况的 match 的更短方法。可选地,if let 可以有一个对应的 else,其中包含如果 if let 中的模式不匹配时要运行的代码。

列表 19-1 表明,也可以混合和匹配 if letelse ifelse if let 表达式。这样做比 match 表达式给我们更多的灵活性,在 match 表达式中,我们只能表达一个要与模式比较的值。此外,Rust 不要求一系列 if letelse ifelse if let 分支中的条件相互关联。

列表 19-1 中的代码根据对几个条件的系列检查来确定将背景设置为哪种颜色。对于此示例,我们创建了具有硬编码值的变量,真实程序可能会从用户输入中接收这些值。

文件名: src/main.rs
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"); } }
列表 19-1:混合 if letelse ifelse if letelse

如果用户指定了喜欢的颜色,则该颜色将用作背景。如果未指定喜欢的颜色且今天是星期二,则背景颜色为绿色。否则,如果用户将其年龄指定为字符串,并且我们可以成功将其解析为数字,则颜色为紫色或橙色,具体取决于数字的值。如果这些条件都不适用,则背景颜色为蓝色。

这种条件结构使我们能够支持复杂的需求。使用我们在这里的硬编码值,此示例将打印 Using purple as the background color

你可以看到 if let 也可以引入新的变量,这些变量以与 match 分支相同的方式遮蔽现有变量:行 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 循环只要模式继续匹配就一直运行。我们在第 17 章中首次看到了 while let 循环,我们在那里使用它来保持循环,只要流产生新值。类似地,在列表 19-2 中,我们展示了一个 while let 循环,它等待线程之间发送的消息,但在这种情况下检查的是 Result 而不是 Option

fn main() { 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}"); } }
列表 19-2:使用 while let 循环打印值,只要 rx.recv() 返回 Ok

此示例打印 1、2 和 3。当我们在第 16 章中看到 recv 时,我们直接解包了错误,或者使用 for 循环将其作为迭代器进行交互。但是,正如列表 19-2 所示,我们也可以使用 while let,因为 recv 方法只要发送者正在生成消息就返回 Ok,然后在发送者端断开连接后产生 Err

for 循环

for 循环中,紧跟关键字 for 的值是一个模式。例如,在 for x in y 中,x 是模式。列表 19-3 演示了如何在 for 循环中使用模式来解构或分解元组作为 for 循环的一部分。

fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{value} is at index {index}"); } }
列表 19-3:在 for 循环中使用模式来解构元组

列表 19-3 中的代码将打印以下内容

$ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s Running `target/debug/patterns` a is at index 0 b is at index 1 c is at index 2

我们使用 enumerate 方法调整迭代器,使其生成一个值和该值的索引,并将它们放入一个元组中。生成的第一个值是元组 (0, 'a')。当此值与模式 (index, value) 匹配时,index 将为 0value 将为 'a',从而打印输出的第一行。

let 语句

在本章之前,我们只明确讨论过将模式与 matchif let 一起使用,但实际上,我们也在其他地方使用了模式,包括在 let 语句中。例如,考虑使用 let 的这种直接变量赋值

#![allow(unused)] fn main() { let x = 5; }

每次你像这样使用 let 语句时,你都在使用模式,尽管你可能没有意识到!更正式地说,let 语句看起来像这样

let PATTERN = EXPRESSION;

在像 let x = 5; 这样的语句中,PATTERN 位置中有一个变量名,变量名只是模式的一种特别简单的形式。Rust 将表达式与模式进行比较,并分配它找到的任何名称。因此,在 let x = 5; 示例中,x 是一种模式,意思是“将此处匹配的内容绑定到变量 x”。因为名称 x 是整个模式,所以此模式实际上意味着“将所有内容绑定到变量 x,无论值是什么。”

为了更清楚地看到 let 的模式匹配方面,请考虑列表 19-4,该列表使用带有 let 的模式来解构元组。

fn main() { let (x, y, z) = (1, 2, 3); }
列表 19-4:使用模式解构元组并一次创建三个变量

在这里,我们将元组与模式匹配。Rust 将值 (1, 2, 3) 与模式 (x, y, z) 进行比较,并看到该值与模式匹配,因此 Rust 将 1 绑定到 x2 绑定到 y3 绑定到 z。你可以将此元组模式视为在其内部嵌套了三个单独的变量模式。

如果模式中元素的数量与元组中元素的数量不匹配,则整体类型将不匹配,我们将收到编译器错误。例如,列表 19-5 显示了尝试将具有三个元素的元组解构为两个变量,这将不起作用。

fn main() { let (x, y) = (1, 2, 3); }
列表 19-5:错误地构造了一个模式,其变量与元组中元素的数量不匹配

尝试编译此代码会导致此类型错误

$ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0308]: mismatched types --> src/main.rs:2:9 | 2 | let (x, y) = (1, 2, 3); | ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})` | | | expected a tuple with 3 elements, found one with 2 elements | = note: expected tuple `({integer}, {integer}, {integer})` found tuple `(_, _)` For more information about this error, try `rustc --explain E0308`. error: could not compile `patterns` (bin "patterns") due to 1 previous error

要修复此错误,我们可以使用 _.. 忽略元组中的一个或多个值,正如你将在 “在模式中忽略值”节中看到的那样。如果问题是我们在模式中有太多变量,则解决方案是通过删除变量来使类型匹配,以便变量的数量等于元组中元素的数量。

函数参数

函数参数也可以是模式。列表 19-6 中的代码声明了一个名为 foo 的函数,该函数接受一个名为 xi32 类型的参数,现在应该看起来很熟悉。

fn foo(x: i32) { // code goes here } fn main() {}
列表 19-6:函数签名在参数中使用模式

x 部分是一个模式!正如我们在 let 中所做的那样,我们可以在函数的参数中将元组与模式匹配。列表 19-7 将元组中的值拆分,当我们将其传递给函数时。

文件名: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({x}, {y})"); } fn main() { let point = (3, 5); print_coordinates(&point); }
列表 19-7:具有解构元组的参数的函数

此代码打印 Current location: (3, 5)。值 &(3, 5) 与模式 &(x, y) 匹配,因此 x 是值 3y 是值 5

我们也可以在闭包参数列表中使用模式,方式与函数参数列表中相同,因为闭包与函数类似,如第 13 章中所讨论的。

至此,你已经看到了几种使用模式的方法,但模式在我们可以使用它们的每个地方的工作方式都不同。在某些地方,模式必须是不可反驳的;在其他情况下,它们可以是可反驳的。我们将在接下来讨论这两个概念。