可以使用模式的所有位置

模式在 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 中的模式不匹配则要运行的代码。

清单 18-1 表明也可以混合和匹配 if letelse ifelse if let 表达式。这样做为我们提供了比 match 表达式更大的灵活性,在 match 表达式中我们只能表达一个与模式进行比较的值。此外,Rust 不要求一系列 if letelse ifelse if let 分支中的条件彼此相关。

清单 18-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");
    }
}

清单 18-1:混合 if letelse ifelse if letelse

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

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

您可以看到 if let 也可以以与 match 分支相同的方式引入阴影变量:if let Ok(age) = age 行引入了一个新的阴影 age 变量,其中包含 Ok 变体内的值。这意味着我们需要将 if age > 30 条件放置在该块内:我们不能将这两个条件组合为 if let Ok(age) = age && age > 30。我们要与 30 进行比较的阴影 age 在新作用域以花括号开始之前是无效的。

使用 if let 表达式的缺点是编译器不会检查详尽性,而使用 match 表达式会进行检查。如果我们省略了最后一个 else 块,因此错过了处理某些情况,编译器将不会提醒我们可能的逻辑错误。

while let 条件循环

if let 的结构类似,while let 条件循环允许 while 循环只要模式继续匹配就运行。在清单 18-2 中,我们编写了一个 while let 循环,该循环使用向量作为堆栈,并以它们被推送的相反顺序打印向量中的值。

fn main() {
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        println!("{top}");
    }
}

清单 18-2:使用 while let 循环在 stack.pop() 返回 Some 时打印值

此示例打印 3、2,然后打印 1。pop 方法从向量中取出最后一个元素并返回 Some(value)。如果向量为空,则 pop 返回 None。只要 pop 返回 Somewhile 循环就会继续运行其块中的代码。当 pop 返回 None 时,循环停止。我们可以使用 while let 从堆栈中弹出每个元素。

for 循环

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

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}

清单 18-3:在 for 循环中使用模式来解构元组

清单 18-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 的模式匹配方面,请考虑清单 18-4,该清单使用带有 let 的模式来解构元组。

fn main() {
    let (x, y, z) = (1, 2, 3);
}

清单 18-4:使用模式解构元组并一次创建三个变量

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

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

fn main() {
    let (x, y) = (1, 2, 3);
}

清单 18-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

为了解决此错误,我们可以使用 _.. 忽略元组中的一个或多个值,如您在 “忽略模式中的值”部分中所见。如果问题是我们模式中的变量太多,则解决方案是通过删除变量来使类型匹配,以便变量的数量等于元组中元素的数量。

函数参数

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

fn foo(x: i32) {
    // code goes here
}

fn main() {}

清单 18-6:函数签名在参数中使用模式

x 部分是一个模式!正如我们使用 let 所做的那样,我们可以在函数的参数中将元组与模式匹配。清单 18-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);
}

清单 18-7:带有解构元组的参数的函数

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

我们也可以在闭包参数列表中使用模式,就像在函数参数列表中一样,因为闭包类似于函数,如第 13 章中所讨论的。

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