可以使用所有 Places 模式


Patterns 在 Rust 中的许多地方都出现了,而你已经在不知不觉中大量使用它们了!本节讨论模式有效的所有位置。


火柴武器


如第 6 章所述,我们在 match 表达式的臂中使用模式。从形式上讲,match 表达式被定义为关键字 match、要匹配的值以及一个或多个 match arm,这些 match arms 由一个模式和一个表达式组成,如果值与该 arm 的模式匹配,则运行该表达式,如下所示:

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


例如,下面是示例 6-5 中的 match 表达式,它与 变量 x 中的选项 <i32> 值:

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


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


match 表达式的一个要求是它们需要详尽无遗,因为必须考虑 match 表达式中值的所有可能性。确保你已经涵盖了所有可能性的一种方法是为最后一个分支使用一个 catchall 模式:例如,匹配任何值的变量名称永远不会失败,从而覆盖所有剩余的情况。


特定的模式 _ 将匹配任何内容,但它永远不会绑定到变量,因此它经常用于最后一个 match arm。例如,当您想要忽略任何未指定的值时,_ 模式可能很有用。我们将在 “忽略 a 中的值 Pattern“部分。


条件 if let 表达式


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


示例 18-1 表明,也可以混合和匹配 if letelse ifelse if let 表达式。这样做比 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 arms 一样引入阴影变量:这行 if let Ok(age) = age 引入了一个新的阴影 age 变量,该变量包含 Ok 变体中的值。这意味着我们需要在该块中放置 if age > 30 条件:我们不能将这两个条件合并到 if let Ok(age) = age && age > 30 中。我们要与 30 进行比较的阴影年龄在新范围以大括号开头之前无效。


使用 if let 表达式的缺点是编译器不会检查穷举性,而使用 match 表达式时,编译器会检查穷举性。如果我们省略了最后一个 else 块,因此错过了处理某些情况,编译器不会提醒我们可能的 logic bug。


while let条件循环


在结构上类似于 if let,while let 条件循环允许 while 循环运行,只要模式继续匹配。在示例 18-2 中,我们编写了一个 while let 循环,它使用 vector 作为堆栈,并按照与值被推送相反的顺序打印 vector 中的值。

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 方法从 vector 中取出最后一个元素并返回 Some(value)。如果 vector 为空,则弹出 返回 None。只要 pop 返回 Some,while 循环就会继续运行其块中的代码。pop 返回 None 时,循环停止。我们可以使用 while let 将每个元素从堆栈中弹出。


循环


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 方法调整迭代器,以便它生成一个值和该值的索引,并将其放入 Tuples 中。生成的第一个值是元组 (0, 'a')。当此值与模式 (index, value) 匹配时, index 将为 0,value 将为 'a',打印输出的第一行。


语句


在本章之前,我们只明确讨论了使用 matchif let,但实际上,我们也在其他地方使用了模式,包括 let 语句。例如,考虑这个简单的变量赋值 let

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


每次使用这样的 let 语句时,您都在使用 patterns,尽管您可能没有意识到!更正式地说,let 语句如下所示:


let PATTERN = 表达式;


在像 let x = 5;这样的语句中,如果变量名称在 PATTERN 槽中,则 变量名称只是模式的一种特别简单的形式。Rust 比较 表达式,并为其找到的任何名称分配。因此,在 设 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。您可以将此 Tuples 模式视为在其内部嵌套三个单独的变量模式。


如果模式中的元素数与元组中的元素数不匹配,则整体类型将不匹配,我们将收到编译器错误。例如,示例 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


为了修复这个错误,我们可以使用 _..,如“忽略 Pattern“部分。如果问题是模式中有太多的变量,那么解决方案是通过删除变量来使类型匹配,使变量的数量等于元组中的元素数量。


功能参数


函数参数也可以是模式。示例 18-6 中的代码,它声明了一个名为 foo 的函数,该函数接受一个名为 x 的 type 参数 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 是值 3,y 是值 5


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


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