# 模式语法

在这一小节中，我们汇总所有模式方面的有效语法，并讨论咱们可能打算使用每种语法的原因和时机。


## 匹配字面值

正如咱们在第 6 章中看到的，咱们可以直接将模式与字面值匹配。下面的代码给出了一些示例：


```rust
    let x = 1;

    match x {
        1 => println! ("壹"),
        2 => println! ("贰"),
        3 => println! ("叁"),
        _ => println! ("万物"),
    }
```

这段代码会打印 `壹`，因为 `x` 中的值为 `1`。当咱们希望代码在其获取到某个特定具体值时执行某项操作时，这种语法非常有用。


## 匹配命名变量

命名变量属于匹配任何值的不可证伪模式，我们在本书中已经多次使用他们。但是，当咱们在 `match`、`if let` 或 `while let` 表达式中使用命名变量时，会存在一定的复杂性。由于这三类表达式都会开启新的作用域，因此作为这些表达式内的模式一部分而声明的变量，会像所有变量一样，遮蔽这些结构外部同名的变量。在下面清单 19-11 中，我们通过 `Some(5)` 声明了一个名为 `x` 的变量，以及有着值 `10` 的变量 `y`。然后，我们对值 `x` 上创建一个 `match` 表达式。请仔细观察匹配支臂中的模式及末尾的 `println!`，并在运行这段代码或继续阅读前，尝试弄清楚这段代码将打印什么。

<a name="listing_19-11"></a>
文件名：`src/main.rs`

```rust
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println! ("得到 50"),
        Some(y) => println! ("已匹配，y = {y}"),
        _ => println! ("默认情况，x = {x:?}"),
    }

    println! ("最后：x = {x:?}, y = {y}");
```

**清单 19-11**：带有一个引入新变量的支臂的 `match` 表达式，该变量遮蔽了现有变量 `y`

我们来逐步分析这个 `match` 表达式运行时会发生什么。第一个匹配支臂中的模式未匹配 `x` 中的定义值，因此代码继续。

第二个匹配支臂中的模式引入了一个名为 `y` 新变量，该变量将匹配 `Some` 值内部的任何值。由于我们处于 `match` 表达式内部的新作用域中，因此这是个新的 `y` 变量，而不是我们在开头声明的值为 `10` 的 `y`。这个新的 `y` 绑定将匹配 `Some` 内的任何值，这正是我们在 `x` 中的值。因此，这个新的 `y` 绑定到 `x` 中 `Some` 的内层值。该值为 `5`，因此这一支臂的表达式执行，并打印 `已匹配，y = 5`。

当 `x` 为 `None` 值而不是 `Some(5)` 时，那么前两个支臂中的模式都不会匹配，因此该值将与下划线 `_` 匹配。我们没有在下划线模式中引入变量 `x`，因此表达式中的 `x` 仍然时未被遮蔽的外层 `x`。在这种假设的情况下，`match` 将打印 `默认情况，x = None`。

当 `match` 表达式执行完毕后，他的作用域结束，而内层的 `y` 的作用域也结束。最后的 `println!` 产生 `最后：x = Some(5), y = 10`。

要创建一个比较外层的 `x` 和 `y` 值的 `match` 表达式，而非引入一个会遮蔽现有变量 `y` 的新变量，我们需要改用匹配卫语句条件，a match guard conditional。我们稍后将在 [通过匹配卫语句添加条件](#匹配卫语句下的额外条件) 小节，讨论到匹配卫语句问题。



## 匹配多个模式

在 `match` 表达式中，咱们可以使用 `|` 语法，即模式 *或* 运算符，匹配多个模式。例如，在下面的代码中，我们将 `x` 的值与匹配支臂匹配，其中第一个支臂有着一个 *或* 选项，这意味着当 `x` 的值匹配该支臂中的任何一个值时，这一支臂的代码将运行：


```rust
    let x = 1;

    match x {
        1 | 2 => println! ("一或二"),
        3 => println! ("三"),
        _ => println! ("万物"),
    }
```

这段代码打印 `一或二`。


## 通过 `..=` 匹配值范围

`..=` 语法允许我们匹配包含起始值和结束值的范围匹配。在下面的代码中，当模式匹配给定范围内的任意值时，该支臂将执行：

```rust
    let x = 5;

    match x {
        1..=5 => println! ("一到五"),
        _ => println! ("万物"),
    }
```

当 `x` 为 `1, 2, 3, 4` 或 `5` 时，第一条支臂将匹配。相比于使用 `|` 运算符，这种语法对于多个匹配值更方便；若使用 `|`，我们就必须指定 `1 | 2 | 3 | 4 | 5`。指定范围要简短得多，尤其是当我们想要匹配  1 到 1000 之间的任何数字时！

编译器会在编译时检查范围是否为空，而由于 Rust 仅能判断 `char` 和数值类型的范围是否为空，因此范围仅允许带有数值或 `char` 值。

下面是个使用 `char` 值范围的示例：

```rust
    let x = 'c';

    match x {
        'a'..='j' => println! ("靠前 ASCII 字母"),
        'k'..='z' => println! ("靠后 ASCII 字母"),
        _ => println! ("其他东西"),
    }
```

Rust 能分辨出 `c` 属于第一个模式的范围，并打印 `靠前 ASCII 字母`。


## 解构以拆分值

我们还可以使用模式来解构结构体、枚举和元组，从而使用这些值的不同部分。我们来逐一分析这些值。


### 结构体

下面清单 19-12 展示了个具有两个字段 `x` 和 `y` 的 `Point` 结构体，我们可以与 `let` 语句一起使用模式拆解他。

<a name="listing_19-12"></a>
文件名：`src/main.rs`

```rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: -7 };

    let Point { x: a, y: b } = p;

    assert_eq! (0, a);
    assert_eq! (-7, b);
}
```

**清单 19-12**：解构结构体的字段为单独的变量

这段代码创建了变量 `a` 和 `b`，匹配结构体 `p` 中的 `x` 与 `y` 字段的值。这个示例表明，模式中的变量名字不必与结构体的字段名字一致。但是，通常会将变量名字与字段名字保持一致，以便更容易记住哪些变量来自哪个字段。由于这种常见用法，并且写下 `let Point { x: x, y: y } = p;` 会包含大量重复，Rust 为匹配结构体字段的模式提供了一种简写形式：咱们只需列出结构体字段的名字，从该模式创建的变量就会具有同样的名字。下面清单 19-13 的行为与清单 19-12 中的代码相同，但在 `let` 模式中创建的变量是 `x` 和 `x`，而不是 `a` 与 `b`。

<a name="listing_19-13"></a>
文件名：`src/main.rs`

```rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: -7 };

    let Point { x, y } = p;

    assert_eq! (0, x);
    assert_eq! (-7, y);
}
```

**清单 19-13**：使用结构体字段简写，解构结构体字段

这段代码创建了变量 `x` 与 `y`，匹配变量 `p` 中的字段 `x` 和 `y`。结果是变量 `x` 与 `y` 包含结构体 `p` 中的值。

我们还可以作为结构体模式一部分的字面值解构，而无需为所有字段都创建变量。这样做允许在创建变量解构其他字段的同时，针对特定值测试某些字段。

在下面清单 19-14 中，我们有个 `match` 表达式，将 `Point` 值分为三种情形：

- 直接位于 `x` 轴上的点（当 `y = 0` 时为真）、
- 位于 `y` 轴上的点，
- 或不在任一轴上的点。

<a name="listing_19-14"></a>
文件名：`src/main.rs`

```rust
    let p = Point { x: 0, y: -7 };

    match p {
        Point { x, y: 0 } => println! ("在 x 轴的 {x} 处"),
        Point { x: 0, y } => println! ("在 y 轴的 {y} 处"),
        Point { x, y } => {
            println! ("不在两个轴上：({x}, {y})");
        }
    }
```

**清单 19-14**：在一个模式中解构和匹配字面值

第一条支臂通过指定当 `y` 字段的值匹配字面值 `0` 时匹配，而将匹配位于 `x` 轴上的任意点。该模式仍会创建一个 `x` 变量，我们可以在这一支臂的代码中使用。

同样地，第二条支臂通过指定当 `x` 字段的值为 `0` 时该字段匹配，从而匹配位于 `y` 轴上的任意点，并为 `y` 字段创建变量 `y`。第三条支臂未指定任何字面值，因此他匹配任何其他 `Point`，并为 `x` 与 `y` 字段创建变量。

在这个示例中，由于 `x` 包含 `0`，因此值 `p` 与第二条支臂匹配，所以这段代码将打印 `在 y 轴的 -7 处`.

请记住，`match` 表达式一旦找到第一个匹配的模式，就会停止检查支臂，因此尽管 `Point { x: 0, y: 0 }` 位于 `x` 轴与 `y` 轴上，但这段代码只会打印 `在 x 轴的 0 处`。


### 枚举

我们在本书中解构过枚举（比如，第 6 章中的 [清单 6-5](../enums_and_pattern_matching/match_control_flow.md#listing_6-5)），但我们还没有明确讨论解构枚举的模式，与枚举中存储的数据的定义方式相对应。例如，在下面清单 19-15 中，我们使用 [清单 6-2](../enums_and_pattern_matching/defining_an_enum.md#listing_6-2) 中的 `Message` 枚举，并编写了个有着模式的 `match` 表达式，将解构每个内层值。

<a name="listing_19-15"></a>
文件名：`src/main.rs`

```rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println! ("Quit 变种没有要解构的数据。");
        }
        Message::Move { x, y } => {
            println! {"在 x 方向移动 {x}，在 y 方向移动 {y}"};
        }
        Message::Write(text) => {
            println! ("文本消息：{text}");
        }
        Message::ChangeColor(r, g, b) => {
            println! ("修改颜色为 红 {r}，绿 {g}，和蓝 {b}");
        }
    }
}
```

**清单 19-15**：解构包含不同类别值的枚举变种

这段代码将打印 `修改颜色为 红 0，绿 160，和蓝 255`。请尝试修改 `msg` 的值，看看其他支臂中的代码运行。

对于没有任何数据的枚举变种，比如 `Message::Quit`，我们无法进一步解构该值。我们只能匹配字面量的 `Message::Quit` 值，并且这一模式中没有变量。

对于类似结构体的枚举变种，比如 `Message::Move`，我们可以使用与我们对结构体指定的类似模式。在变种名字之后，我们放置了一对花括号，然后通过变量列出字段，以便我们分解各个部分已在这一支臂的代码中使用。这里我们使用了与清单 19-13 中一样的简写形式。

对于类似元组的枚举变种，比如包含一个元素的元组的 `Message::Write` 和包含三个元素的元组的 `Message::ChangeColor`，该模式与我们用于匹配元组的模式类似。模式中的变量数量必须要与我们要匹配的变种中的元素数量一致。


### 嵌套结构体与枚举

到目前为止，我们的示例都只匹配了一层深度的结构体与枚举，但匹配也可以用于嵌套项目！例如，我们可以重构清单 19-15 中的代码，以支持 `ChangeColor` 消息中的 RGB 与 HSV 颜色，如下清单 19-16 中所示。

<a name="listing_19-16"></a>
文件名：`src/main.rs`

```rust
enum Color {
    Rgb(u32, u32, u32),
    Hsv(u32, u32, u32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println! ("修改颜色为：红 {r}、绿 {g} 及蓝 {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println! ("修改颜色改为：色调 {h}、饱和度 {s}、颜色值 {v}");
        }
        _ => (),
    }
}
```

**清单 19-16**：对嵌套枚举的匹配

`match` 表达式中第一个支臂的模式，匹配包含 `Color::Rgb` 变种的 `Message::ChangeColor` 枚举变种；然后，该模式会绑定到三个内部 `i32` 值。第二支臂的模式同样匹配 `Message::ChangeColor` 枚举变种，但内部枚举匹配的是 `Color::Hsv`。即使涉及两个枚举，我们也可以在一个 `match` 表达式中指定这些复杂条件。


### 结构体和元组

咱们可以更复杂的方式混合、匹配并嵌套解构模式。以下示例展示了一个复杂的解构，其中我们将结构体与元组嵌套在元组内，并解构出所有原始值：


```rust
    let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10});
```

这段代码让我们可以将复杂类型分解为各个组成部分，从而我们可以单独使用我们感兴趣的值。

对模式解构是一种彼此分开地使用值的部分的便捷方式，例如结构体中每个字段中的值。


## 忽略模式中的值

咱们已经看到，有时为了获得全包模式，而忽略模式中的值很有用，比如在 `match` 的最后支臂中，全包模式实际上不执行任何操作，但确实考虑了所有剩余的可能值。忽略模式中整个值或部分值的方式有几种：使

- 使用 `_` 模式（咱们已经见过）、
- 在另一个模式中使用 `_` 模式、
- 使用以下划线开头的名字，
- 或使用 `..` 忽略值的剩余部分。


我们来探讨怎样及为何使用这些模式。


### 通过 `_` 忽略整个值

我们已将下划线作为通配符模式使用过，他将匹配任何值但不会绑定到该值。这作为 `match` 表达式中的最后一个支臂尤为有用，但我们也可在任何模式中使用他，包括函数参数，如下清单 19-17 中所示。

<a name="listing_19-17"></a>
文件名：`src/main.rs`

```rust
fn foo(_: i32, y: i32) {
    println! ("这段代码仅使用参数 y：{y}");
}

fn main() {
    foo(3, 4);
}
```

**清单 19-17**：在函数签名中使用 `_`

这段代码将完全忽略作为第一个参数传递的值 `3`，并将打印 `这段代码仅使用参数 y：4`。

在大多数情况下，当咱们不需要某个特定的函数参数时，咱们会修改函数签名，使其不包含未使用的参数。忽略函数参数在某些情况下特别有用，例如，当咱们实现某个特质时，咱们需要特定的类型签名，而实现中的函数体并不需要其中某个参数。忽略函数参数后，咱们可以避免收到关于未使用的函数参数的编译器告警。


### 通过嵌套的 `_` 忽略值的部分

我们还可以在另一个模式内使用 `_`，来仅忽略值的一部分，例如，当我们仅希望测试值的一部分，而在我们打算运行的相应代码中未用到其他部分时。下面清单 19-18 展示了负责管理某种设置值的代码。业务要求为，不应允许用户覆盖设置的现有自定义设置，但允许用户取消该设置，并在当前未设置时为其赋予新值。


<a name="listing_19-18"></a>
```rust
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println! ("无法覆盖现有的自定义值");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }

    println! ("设置为 {setting_value:?}");
```

**清单 19-18**：当我们不需要使用 `Some` 中的值时，在匹配 `Some` 变种的模式内使用下划线


这段代码将打印 `无法覆写现有的自定义值`，及随后的 `设置为 Some(5)`。在第一个匹配支臂中，我们无需匹配或使用任一 `Some` 变种内的值，但我们确实需要测试 `setting_value` 和 `new_setting_value` 均为 `Some` 变种时的情形。在这一情形下，我们打印不修改 `setting_value` 的原因，并且他不会被更改。

在第二个支臂中以 `_` 模式表示的所有其他情形下（当 `setting_value` 或 `new_setting_value` 为 `None` 时），我们希望允许 `new_setting_value` 成为 `setting_value`。

我们还可以在一个模式中的多个位置，使用下划线来忽略特定值。下面清单 19-19 展示了忽略五个项目的元组中，第二和第四个值的示例。

<a name="listing_19-19"></a>
```rust
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println! ("一些数字：{first}, {third}, {fifth}");
        }
    }
```

**清单 19-19**：忽略元组的多个部分


这段代码将打印 `一些数字：2, 8, 32`，而值 `4` 与 `16` 将被忽略。


### 通过 `_` 开头的变量名字，忽略未使用变量

当咱们创建一个变量但未在任何地方使用他时，Rust 通常会发出告警，因为未使用变量可能是个 bug。然而，有时创建一个咱们使用的变量是很有用的，比如当咱们正在构造原型或刚刚开始一个项目时。在这种情况下，咱们可以通过让变量以下划线开头，告诉 Rust 不要对这个使用的变量发出告警。在下面清单 19-20 中，我们创建了两个未使用的变量，但编译此代码时，我们应该只会收到关于其中一个变量的告警。

<a name="listing_19-20"></a>
```rust
fn main() {
    let _x = 5;
    let y = 10;
}
```

**清单 19-20**：让变量名以下划线开头，以避免收到未使用变量的告警

在这里，我们会得到关于未用到变量 `y` 的告警，但不会收到关于未使用 `_x` 的告警。

请注意，仅使用 `_` 和使用以下划线开头的名字之间存在细微差别。语法 `_x` 仍会绑定值到变量，而 `_` 则完全不会绑定。为了展示这种区别很重要的情形，下面清单 19-21 将为我们提供一个报错。

<a name="listing_19-21"></a>
```rust
    let s = Some(String::from("你好！"));

    if let Some(_s) = s {
        println! ("找到字符串");
    }

    println! ("{s:?}");
```

**清单 19-21**：以下划线开头的未使用变量仍会绑定值，这就会取得该值的所有权

我们将收到报错，因为 `s` 值仍将被迁移到 `_s` 中，这会阻止我们再次使用 `s`。然而，单独使用下划线本身并不会绑定到值。下面清单 19-22 将无任何报错地编译，因为`s` 不会迁移到 `_` 中。

<a name="listing_19-22"></a>
```rust
    let s = Some(String::from("你好！"));

    if let Some(_) = s {
        println! ("找到字符串");
    }

    println! ("{s:?}");
```

**清单 19-22**：使用下划线不会绑定值

这段代码正常运行，因为我们从未绑定 `s` 到任何变量；他未被迁移。


### 通过 `..` 忽略值的剩余部分

对于包含多个部分的值，我们可以使用 `..` 语法，来使用特定部分而忽略其余部分，从而避免需要为每个忽略的值逐一列出下划线。`..` 模式会忽略我们在模式的剩余部分中未曾显式匹配的任何部分。在下面清单 19-23 中，我们有个 `Point` 结构体，保存三维空间中的坐标。在 `match` 表达式中，我们只打算对 `x` 坐标操作，而忽略 `y` 与 `z` 字段中的值。

<a name="listing_19-23"></a>
```rust
    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

    let origin = Point { x: 0, y: 0, z: 0 };

    match origin {
        Point { x, .. } => println! ("x 为 {x}"),
    }
```

**清单 19-23**：使用 `..` 忽略 `Point` 中除 `x` 外的所有字段

我们列出值 `x`，然后仅包含 `..` 模式。这比列出 `y: _` 和 `z: _` 更快，特别是当我们在处理包含大量字段的结构体，而只有一两个字段是相关的时。

语法 `..` 将扩展为所需数量的值。下面清单 19-24 展示了怎样对元组使用 `..`。

<a name="listing_19-24"></a>
文件名：`src/main.rs`

```rust
    let numbers = (2, 4, 6, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println! ("一些数字： {first}, {last}");
        }
    }
```

**清单 19-24**：仅匹配元组中的第一个和最后一个值，而忽略所有其他值

在这段代码中，第一个和最后一个值分别与 `first` 和 `last` 匹配。`..` 将匹配并忽略中间的所有值。

不过，使用 `..` 必须没有歧义。当不清楚哪些值要匹配，哪些值应该被忽略时，Rust 将给予我们一个报错。下面清单 19-25 展示了 `..` 歧义用法的示例，因此他不会编译。

<a name="listing_19-25"></a>
文件名：`src/main.rs`

```rust
fn main() {
    let numbers = (2, 4, 6, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println! ("一些数字：{second}");
        }
    }
}
```

**清单 19-25**：尝试以歧义的方式使用 `..`

当我们编译这个示例时，我们会得到下面这个报错：

```console
$ cargo run
   Compiling patterns v0.1.0 (/home/hector/rust-lang-zh_CN/projects/patterns)
error: `..` can only be used once per tuple pattern
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

error: could not compile `patterns` (bin "patterns") due to 1 previous error
```

Rust 不可能确定出在将某个值与 `second` 匹配之前，应忽略元组中的多少个值，以及之后还应忽略多少个值。这段代码可能意味着我们想要忽略 `2`，将 `second` 与 `4` 绑定，然后忽略 `8`、`16` 和 `32`；或者我们想要忽略 `2` 和 `4`，将 `second` 与 `8` 绑定，然后忽略 `16` 和 `32`；等等。变量名 `second` 对 Rust 而言并无特殊含义，所以我们会得到一个编译器报错，因为在像这样在两个地方使用 `..` 是不明确的。


## 匹配卫语句下的额外条件

所谓 *匹配卫语句，match guard*，属于额外的 `if` 条件，指定于 `match` 支臂之后，他也必须匹配，该支臂才能被选中。对于表达比单独模式所允许的更复杂的概念，匹配卫语句非常有用。但请注意，他们仅可在 `match` 表达式中使用，而不能用于 `if let` 或 `while let` 表达式。

这种条件可以使用模式中创建的变量。下面清单 19-26 展示了一个 `match` 表达式，其中第一个支臂有着模式 `Some(x)`，并且还有着 `if x % 2 == 0` 的匹配卫语句（当数字为偶数时该条件将为 `true` ）。

<a name="listing_19-26"></a>
```rust
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println! ("数字 {x} 为偶数"),
        Some(x)  => println! ("数字 {x} 为奇数"),
        None => (),
    }
```

**清单 19-26**：添加匹配卫语句到模式

这个示例将打印 `数字 4 为偶数`。当 `num` 与第一个支臂中的模式比较时，他会匹配，因为 `Some(4)` 匹配 `Some(x)`。然后，匹配卫语句会检查 `x` 除以 2 的余数是否等于 0，由于结果为 0，因此第一个支臂被选中。

若 `num` 改为 `Some(5)`，那么第一个支臂中的匹配卫语句将为 `false`，因为 5 除以 2 的余数为 1，不等于 0。此时 Rust 将转到第二个支臂，该支臂会匹配，因为他没有匹配卫语句，而因此会匹配任何 `Some` 变种。

由于无法在支臂内部表达 `if x % 2 == 0` 这一条件，因此匹配卫语句给予了我们表达这种逻辑的能力。这种额外表达能力的的缺点在于，当涉及匹配卫语句时，编译器不会尝试检查是否穷尽。

在讨论 [清单 19-11](#listing_19-11) 时，我们提到可以使用匹配卫语句来解决模式遮蔽问题，pattern-shadowing problem。回顾以下，我们在 `match` 表达式中的模式内部创建了一个新变量，而不是使用 `match` 外部的变量。这个新变量意味着我们无法测试外层变量的值。下面清单 19-27 展示了我们可以怎样使用匹配卫语句解决这个问题。

<a name="listing_19-27"></a>
文件名：`src/main.rs`

```rust
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println! ("得到 50"),
        Some(n) if n == y => println! ("匹配，n = {n}"),
        _ => println! ("默认情况，x = {x:?}"),
    }

    println! ("最后：x = {x:?}, y = {y}");
}
```

**清单 19-27**：使用匹配卫语句测试与外层变量的相等性

这段代码现在将打印 `默认情况，x = Some(5)`。第二个匹配支臂中的模式不会引入将遮蔽外层的 `y` 的新变量 `y`，这意味着我们可以在匹配卫语句中使用外层的 `y`。我们没有将模式指定为 `Some(y)`，这将遮蔽外层的 `y`，而是指定 `Some(n)`。这会创建一个新变量 `n`，他不会遮蔽任何变量，因为 `match` 外部没有变量 `n`。

匹配卫语句 `if n == y` 不是个模式，因此不会引入新变量。这个 `y` *是* 外层的 `y`，而不是一个会遮蔽他的新的 `y`，我们可以通过比较 `n` 和 `y`，查找与外层 `y` 具有相同的值。

咱们还可在匹配卫语句中使用 *或* 运算符 `|`，来指定多个模式；匹配卫语句条件将应用与所有模式。下面清单 19-28 展示了使用 `|` 的模式与匹配卫语句组合时的优先级。这个示例的重要部分是，`if y` 匹配卫语句会应用于 `4`、`5` *和* `6`，尽管看起来 `if y` 只会应用到 `6`。

<a name="listing_19-28"></a>
```rust
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println! ("是"),
        _ => println! ("否"),
    }
```

**清单 19-28**：将多个模式与匹配卫语句结合

匹配条件指出，只有当 `x` 的值等于 `4`、`5` 或 `6`， *且* `y` 为 `true` 时，这个支臂才会匹配。当这段代码运行时，由于 `x` 为 `4`，第一个支臂的模式会匹配，但匹配卫卫语句的 `if y` 为`false`，从而第一个支臂未被选中。代码会转到第二个支臂，他确实匹配，进而这个程序打印 `否`。原因是 `if` 条件会应用到整个模式 `4 | 5 | 6`，而不仅仅是最后一个值 `6`。换句话说，匹配卫语句相对于模式的优先级的行为如下：

```rust
(4 | 5 | 6) if y => ...
```

而不是这样：

```rust
4 | 5 | (6 if y) => ...
```

运行代码后，优先级行为显而易见：若匹配卫语句仅应用于使用 `|` 运算符指定的值列表中的最后一个值，那么该支臂就会匹配，进而这个程序就会打印 `是`。


## 使用 `@` 绑定

*地址* 运算符 `@` 让我们可以在对某个值进行模式匹配测试的同时，创建一个保存值的变量。在下面清单 19-29 中，我们打算测试 `Message::Hello` 的 `id` 字段，是否范围 `3..=7` 内。我们还希望绑定该值到变量 `id`，以便在与该支臂相关的代码中使用他。

<a name="listing_19-29"></a>
```rust
    enum Message {
        Hello { id: u32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello { id: id @ 3..=7 } => {
            println! ("找到位于范围内的 id: {id}")
        }
        Message::Hello { id: 10..=12 } => {
            println! ("找到位于另一范围内 id")
        }
        Message::Hello { id } => println! ("找到别的 id: {id}"),
    }
```

**清单 19-29**：使用 `@` 绑定到模式中的值，同时测试该值

这个示例将打印 `找到位于范围内 id: 5`。通过在范围 `3..=7` 之前指定 `id @`，我们将于名为 `id` 的变量中，捕获匹配该范围匹配的任何值，同时测试该值是否匹配该范围模式。

在第二个支臂中，我们仅在模式中指定了一个范围，与该支臂关联的代码没有包含 `id` 字段实际值的变量。`id` 字段的值可能是 10、11 或 12，但与该模式相关的代码并不知道具体是哪个值。由于我们没有保存 `id` 值在变量中，因此模式代码无法使用 `id` 字段的值。

在最后一个支臂中，虽然我们指定了一个不带范围的变量，但在该支臂的代码中，我们确实可以通过一个名为 `id` 的变量使用该值。原因在于我们使用了结构体字段的简写语法，the struct field shorthand syntax。但与前两个支臂不同，我们在这个支臂中并未对`id` 字段应用任何测试：任何值都会匹配这个模式。

使用 `@` 让我们可以在同一个模式中测试值并保存其到变量中。


# 本章小结

Rust 的模式在区分不同类别的数据方面非常有用。当在 `match` 表达式中使用时，Rust 就会确保咱们的模式涵盖所有可能的值，否则程序将无法编译。`let` 语句和函数参数中的模式，使这些结构更为有用，可以将值解构为更小的部分，并将这些部分赋值给变量。我们可以创建简单或复杂的模式来满足我们的需求。

接下来，在本书的倒数第二章中，我们将探讨 Rust 各种特性的一些高级方面。


（End）


