Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

模式可以使用的所有地方

All the Places Patterns Can Be Used

模式出现在 Rust 的许多地方,你可能已经在不知不觉中经常使用它们了!本节讨论了模式所有有效的使用场所。

Patterns pop up in a number of places in Rust, and you’ve been using them a lot without realizing it! This section discusses all the places where patterns are valid.

match 分支

match Arms

正如第 6 章中所讨论的,我们在 match 表达式的分支中使用模式。正式地讲,match 表达式由关键字 match、一个待匹配的值以及一个或多个匹配分支组成,每个分支包含一个模式和在值匹配该分支模式时运行的表达式,如下所示:

As discussed in Chapter 6, we use patterns in the arms of match expressions. Formally, match expressions are defined as the keyword match, a value to match on, and one or more match arms that consist of a pattern and an expression to run if the value matches that arm’s pattern, like this:

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

例如,这是来自示例 6-5 的 match 表达式,它对变量 x 中的 Option<i32> 值进行匹配:

For example, here’s the match expression from Listing 6-5 that matches on an Option<i32> value in the variable x:

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

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

The patterns in this match expression are the None and Some(i) to the left of each arrow.

match 表达式的一个要求是它们必须是穷尽的 (exhaustive),即 match 表达式中值的所有可能性都必须被考虑到。确保覆盖每种可能性的一种方法是在最后一个分支使用一个通配模式:例如,一个匹配任何值的变量名永远不会失败,因此可以覆盖所有剩余的情况。

One requirement for match expressions is that they need to be exhaustive in the sense that all possibilities for the value in the match expression must be accounted for. One way to ensure that you’ve covered every possibility is to have a catch-all pattern for the last arm: For example, a variable name matching any value can never fail and thus covers every remaining case.

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

The particular pattern _ will match anything, but it never binds to a variable, so it’s often used in the last match arm. The _ pattern can be useful when you want to ignore any value not specified, for example. We’ll cover the _ pattern in more detail in “Ignoring Values in a Pattern” later in this chapter.

let 语句

let Statements

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

Prior to this chapter, we had only explicitly discussed using patterns with match and if let, but in fact, we’ve used patterns in other places as well, including in let statements. For example, consider this straightforward variable assignment with let:

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

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

Every time you’ve used a let statement like this you’ve been using patterns, although you might not have realized it! More formally, a let statement looks like this:

let PATTERN = EXPRESSION;

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

In statements like let x = 5; with a variable name in the PATTERN slot, the variable name is just a particularly simple form of a pattern. Rust compares the expression against the pattern and assigns any names it finds. So, in the let x = 5; example, x is a pattern that means “bind what matches here to the variable x.” Because the name x is the whole pattern, this pattern effectively means “bind everything to the variable x, whatever the value is.”

为了更清楚地看到 let 的模式匹配方面,请考虑示例 19-1,它使用带有 let 的模式来解构一个元组。

To see the pattern-matching aspect of let more clearly, consider Listing 19-1, which uses a pattern with let to destructure a tuple.

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

在这里,我们将一个元组与一个模式进行匹配。Rust 将值 (1, 2, 3) 与模式 (x, y, z) 进行比较,发现该值匹配该模式——即,它发现两者的元素数量相同——因此 Rust 将 1 绑定到 x2 绑定到 y3 绑定到 z。你可以将这个元组模式看作是在其内部嵌套了三个单独的变量模式。

Here, we match a tuple against a pattern. Rust compares the value (1, 2, 3) to the pattern (x, y, z) and sees that the value matches the pattern—that is, it sees that the number of elements is the same in both—so Rust binds 1 to x, 2 to y, and 3 to z. You can think of this tuple pattern as nesting three individual variable patterns inside it.

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

If the number of elements in the pattern doesn’t match the number of elements in the tuple, the overall type won’t match and we’ll get a compiler error. For example, Listing 19-2 shows an attempt to destructure a tuple with three elements into two variables, which won’t work.

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

尝试编译这段代码会导致如下类型错误:

Attempting to compile this code results in this type error:

$ 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

要修复该错误,我们可以使用 _.. 忽略元组中的一个或多个值,你将在“在模式中忽略值”部分看到这一点。如果问题在于模式中的变量过多,解决方案是删除变量使类型匹配,从而使变量数量等于元组中的元素数量。

To fix the error, we could ignore one or more of the values in the tuple using _ or .., as you’ll see in the “Ignoring Values in a Pattern” section. If the problem is that we have too many variables in the pattern, the solution is to make the types match by removing variables so that the number of variables equals the number of elements in the tuple.

条件 if let 表达式

Conditional if let Expressions

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

In Chapter 6, we discussed how to use if let expressions mainly as a shorter way to write the equivalent of a match that only matches one case. Optionally, if let can have a corresponding else containing code to run if the pattern in the if let doesn’t match.

示例 19-3 展示了也可以混合搭配使用 if letelse ifelse if let 表达式。这样做比 match 表达式具有更大的灵活性,在 match 中我们只能表达一个要与模式进行比较的值。此外,Rust 不要求一系列 if letelse ifelse if let 分支中的条件相互关联。

Listing 19-3 shows that it’s also possible to mix and match if let, else if, and else if let expressions. Doing so gives us more flexibility than a match expression in which we can express only one value to compare with the patterns. Also, Rust doesn’t require that the conditions in a series of if let, else if, and else if let arms relate to each other.

示例 19-3 中的代码根据对多个条件的一系列检查来确定背景颜色。对于这个例子,我们创建了带有硬编码值的变量,实际程序可能会从用户输入中接收这些值。

The code in Listing 19-3 determines what color to make your background based on a series of checks for several conditions. For this example, we’ve created variables with hardcoded values that a real program might receive from user input.

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 the user specifies a favorite color, that color is used as the background. If no favorite color is specified and today is Tuesday, the background color is green. Otherwise, if the user specifies their age as a string and we can parse it as a number successfully, the color is either purple or orange depending on the value of the number. If none of these conditions apply, the background color is blue.

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

This conditional structure lets us support complex requirements. With the hardcoded values we have here, this example will print Using purple as the background color.

你可以看到 if let 也可以引入新变量,这些新变量会以与 match 分支相同的方式遮蔽 (shadow) 现有变量:行 if let Ok(age) = age 引入了一个新的 age 变量,其中包含 Ok 变体中的值,遮蔽了现有的 age 变量。这意味着我们需要将 if age > 30 条件放在该代码块内:我们不能将这两个条件合并为 if let Ok(age) = age && age > 30。我们要与 30 比较的新 age 直到大括号开始的新作用域才有效。

You can see that if let can also introduce new variables that shadow existing variables in the same way that match arms can: The line if let Ok(age) = age introduces a new age variable that contains the value inside the Ok variant, shadowing the existing age variable. This means we need to place the if age > 30 condition within that block: We can’t combine these two conditions into if let Ok(age) = age && age > 30. The new age we want to compare to 30 isn’t valid until the new scope starts with the curly bracket.

使用 if let 表达式的缺点是编译器不会检查穷尽性,而在 match 表达式中它会检查。如果我们省略了最后的 else 块,从而漏掉了一些情况的处理,编译器将不会提醒我们可能存在的逻辑错误。

The downside of using if let expressions is that the compiler doesn’t check for exhaustiveness, whereas with match expressions it does. If we omitted the last else block and therefore missed handling some cases, the compiler would not alert us to the possible logic bug.

while let 条件循环

while let Conditional Loops

if let 的构造类似,while let 条件循环允许 while 循环只要模式继续匹配就一直运行。在示例 19-4 中,我们展示了一个 while let 循环,它等待线程之间发送的消息,但在这种情况下检查的是 Result 而不是 Option

Similar in construction to if let, the while let conditional loop allows a while loop to run for as long as a pattern continues to match. In Listing 19-4, we show a while let loop that waits on messages sent between threads, but in this case checking a Result instead of an 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}");
    }
}

这个例子打印 12,然后是 3recv 方法从通道的接收端取出第一条消息并返回一个 Ok(value)。当我们第一次在第 16 章看到 recv 时,我们直接解包了错误,或者使用 for 循环将其作为迭代器进行交互。然而,如示例 19-4 所示,我们也可以使用 while let,因为只要发送端存在,recv 方法在每次消息到达时都会返回一个 Ok,然后在发送端断开连接后产生一个 Err

This example prints 1, 2, and then 3. The recv method takes the first message out of the receiver side of the channel and returns an Ok(value). When we first saw recv back in Chapter 16, we unwrapped the error directly, or we interacted with it as an iterator using a for loop. As Listing 19-4 shows, though, we can also use while let, because the recv method returns an Ok each time a message arrives, as long as the sender exists, and then produces an Err once the sender side disconnects.

for 循环

for Loops

for 循环中,直接跟在关键字 for 之后的值是一个模式。例如,在 for x in y 中,x 就是模式。示例 19-5 演示了如何在 for 循环中使用模式来解构(或分解)元组作为 for 循环的一部分。

In a for loop, the value that directly follows the keyword for is a pattern. For example, in for x in y, the x is the pattern. Listing 19-5 demonstrates how to use a pattern in a for loop to destructure, or break apart, a tuple as part of the for loop.

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

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

示例 19-5 中的代码将打印以下内容:

The code in Listing 19-5 will print the following:

$ 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 将是 0,value 将是 'a',打印出输出的第一行。

We adapt an iterator using the enumerate method so that it produces a value and the index for that value, placed into a tuple. The first value produced is the tuple (0, 'a'). When this value is matched to the pattern (index, value), index will be 0 and value will be 'a', printing the first line of the output.

函数参数

Function Parameters

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

Function parameters can also be patterns. The code in Listing 19-6, which declares a function named foo that takes one parameter named x of type i32, should by now look familiar.

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

fn main() {}

x 部分就是一个模式!就像我们处理 let 一样,我们可以在函数参数中将元组与模式进行匹配。示例 19-7 在将元组传递给函数时将其中的值拆分。

The x part is a pattern! As we did with let, we could match a tuple in a function’s arguments to the pattern. Listing 19-7 splits the values in a tuple as we pass it to a function.

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

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

This code prints Current location: (3, 5). The values &(3, 5) match the pattern &(x, y), so x is the value 3 and y is the value 5.

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

We can also use patterns in closure parameter lists in the same way as in function parameter lists because closures are similar to functions, as discussed in Chapter 13.

到目前为止,你已经见过了几种使用模式的方法,但模式在我们能使用它们的每个地方的工作方式并不完全相同。在某些地方,模式必须是不可反驳的 (irrefutable);在其他情况下,它们可以是可反驳的 (refutable)。接下来我们将讨论这两个概念。

At this point, you’ve seen several ways to use patterns, but patterns don’t work the same in every place we can use them. In some places, the patterns must be irrefutable; in other circumstances, they can be refutable. We’ll discuss these two concepts next.