键盘快捷键

按 ← 或 → 在章节之间导航

按 S 或 / 在书中搜索

按 ? 显示此帮助

按 Esc 隐藏此帮助

模式语法

在本节中,我们汇总了模式中所有有效的语法,并讨论每种语法 的使用场景和原因。

匹配字面量

如你在第 6 章所见,可以直接将模式与字面量匹配。以下代码给出了 一些示例:

fn main() {
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

这段代码打印 one,因为 x 中的值是 1。当你希望 代码在收到某个具体的值时执行某项操作时,这种语法就很有用。

匹配命名变量

命名变量是能匹配任何值的无可反驳模式,我们在本书中已多次使用过 它们。然而,当你在 match、if let 或 while let 表达式中 使用命名变量时会出现一个复杂情况。因为这些表达式各自都会开启一个 新的作用域,在这些表达式中作为模式一部分声明的变量将会遮蔽外部 同名的变量——这一点对所有变量都成立。在示例 19-11 中,我们声明了 一个名为 x、值为 Some(5) 的变量,以及一个值为 10 的变量 y。然后我们针对值 x 创建了一个 match 表达式。请先查看 match 分支中的模式和末尾的 println!,在运行代码或继续阅读之前, 试着推断这段代码会打印什么。

Filename: src/main.rs
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {y}"),
        _ => println!("Default case, x = {x:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");
}
示例 19-11:一个 match 表达式,其中一个分支引入了一个新变量,遮蔽了现有变量 y

让我们逐步看看 match 表达式运行时会发生什么。第一个 match 分支中的模式不匹配 x 的定义值,因此代码继续往下执行。

第二个 match 分支中的模式引入了一个名为 y 的新变量, 它将匹配 Some 值中的任何值。因为我们在 match 表达式内部的 新作用域中,这是一个新的 y 变量,不是我们一开始声明的值为 10 的那个 y。这个新的 y 绑定会匹配 Some 中的任何 值,这正是 x 中的内容。因此,这个新 y 绑定到 x 中 Some 的内部值。该值为 5,所以该分支的表达式执行 并打印 Matched, y = 5。

如果 x 是 None 值而不是 Some(5),那么前两个分支的 模式都不会匹配,因此值会匹配到下划线。我们没有在下划线分支的 模式中引入 x 变量,所以表达式中的 x 仍然是未被遮蔽的外部 x。在这种假设情况下,match 会打印 Default case, x = None。

当 match 表达式结束后,其作用域也随之结束,内部 y 的 作用域也是如此。最后一个 println! 输出 at the end: x = Some(5), y = 10。

要创建一个比较外部 x 和 y 值的 match 表达式, 而不是引入一个遮蔽现有 y 变量的新变量,我们需要改用 带有条件的 match 守卫(match guard)。我们将在「使用 match 守卫 添加条件」一节中讨论 match 守卫。

匹配多个模式

在 match 表达式中,你可以使用 | 语法(即模式或运算符) 来匹配多个模式。例如在以下代码中,我们将 x 的值与各 match 分支匹配,其中第一个分支有一个或选项,意思是如果 x 的值 匹配该分支中任一值,该分支的代码就会运行:

fn main() {
    let x = 1;

    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

这段代码打印 one or two。

使用 ..= 匹配值范围

..= 语法允许我们匹配闭区间范围内的值。在以下代码中, 当模式匹配给定范围内的任何值时,该分支就会执行:

fn main() {
    let x = 5;

    match x {
        1..=5 => println!("one through five"),
        _ => println!("something else"),
    }
}

如果 x 是 1、2、3、4 或 5,第一个分支就会匹配。 这种语法对于多个匹配值而言比使用 | 运算符表达同样的意思 更方便;如果使用 |,我们就不得不写成 1 | 2 | 3 | 4 | 5。 指定一个范围要简洁得多,特别是当我们想匹配比如 1 到 1000 之间的任何数字时!

编译器会在编译时检查范围不为空,而由于 Rust 唯一能判断 范围是否为空的类型是 char 和数值类型,所以范围只允许 用于数值或 char 值。

下面是一个使用 char 值范围的示例:

fn main() {
    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }
}

Rust 能判断 'c' 在第一个模式的范围内,并打印 early ASCII letter。

解构以拆分值

我们也可以使用模式来解构结构体、枚举和元组,以便使用这些值的 不同部分。让我们逐一讲解。

结构体

示例 19-12 展示了一个具有两个字段 x 和 y 的 Point 结构体, 我们可以使用带 let 语句的模式将其拆分。

Filename: src/main.rs
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 和 y 而不是 a 和 b。

Filename: src/main.rs
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 轴上的点(x = 0),以及不在任何轴上的点。

Filename: src/main.rs
struct Point {
    x: i32,
    y: i32,
}

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

    match p {
        Point { x, y: 0 } => println!("On the x axis at {x}"),
        Point { x: 0, y } => println!("On the y axis at {y}"),
        Point { x, y } => {
            println!("On neither axis: ({x}, {y})");
        }
    }
}
示例 19-14:在一个模式中同时解构和匹配字面量值

第一个分支通过指定 y 字段在其值匹配字面量 0 时才匹配, 来匹配任何位于 x 轴上的点。该模式仍然创建了一个 x 变量,我们可以在该分支的代码中使用它。

类似地,第二个分支通过指定 x 字段在其值为 0 时 匹配,并为 y 字段的值创建变量 y,来匹配任何位于 y 轴上的点。第三个分支没有指定任何字面量,所以它 匹配任何其他 Point,并为 x 和 y 字段都创建变量。

在这个例子中,由于 x 包含 0,值 p 匹配第二个分支, 因此这段代码会打印 On the y axis at 7。

记住,match 表达式在找到第一个匹配的模式后就会停止 检查分支,因此即使 Point { x: 0, y: 0 } 同时在 x 轴和 y 轴上,这段代码也只会打印 On the x axis at 0。

枚举

我们在本书中已解构过枚举(例如第 6 章的示例 6-5),但尚未 明确讨论解构枚举的模式如何与枚举内存储数据的定义方式 相对应。作为示例,在示例 19-15 中我们使用示例 6-2 中的 Message 枚举,并编写一个 match,其模式将解构每个 内部值。

Filename: src/main.rs
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

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

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
    }
}
示例 19-15:解构持有不同类型值的枚举变体

这段代码将打印 Change color to red 0, green 160, and blue 255。 试着修改 msg 的值,观察其他分支的代码运行。

对于没有任何数据的枚举变体(如 Message::Quit),我们 无法进一步解构值。我们只能匹配字面量 Message::Quit 值, 模式中没有变量。

对于类结构体的枚举变体(如 Message::Move),我们可以使用 类似于匹配结构体的模式。在变体名称后放置花括号,然后列出 带变量的字段,这样我们就可以将这些部分拆分出来用于 该分支的代码。这里我们使用了与示例 19-13 相同的简写形式。

对于类元组的枚举变体(如持有一个元素的元组的 Message::Write,和持有三个元素的元组的 Message::ChangeColor),模式类似于我们用来匹配元组的模式。 模式中的变量数量必须与我们匹配的变体中的元素数量一致。

嵌套的结构体和枚举

到目前为止,我们的例子都是匹配一层深度的结构体或枚举, 但匹配也适用于嵌套项!例如,我们可以重构示例 19-15 中的 代码,使 ChangeColor 消息支持 RGB 和 HSV 颜色,如 示例 19-16 所示。

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

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!("Change color to red {r}, green {g}, and blue {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to hue {h}, saturation {s}, value {v}");
        }
        _ => (),
    }
}
示例 19-16:匹配嵌套枚举

match 表达式中第一个分支的模式匹配包含 Color::Rgb 变体 的 Message::ChangeColor 枚举变体;然后模式绑定到三个内部 i32 值。第二个分支的模式也匹配 Message::ChangeColor 枚举变体,但内部枚举改为匹配 Color::Hsv。我们可以在 一个 match 表达式中指定这些复杂的条件,即使涉及 两个枚举。

结构体和元组

我们可以以更复杂的方式混合、匹配和嵌套解构模式。下面的示例 展示了一个复杂的解构:我们将结构体和元组嵌套在元组内部, 并将所有原始值解构出来:

fn main() {
    struct Point {
        x: i32,
        y: i32,
    }

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

这段代码让我们能将复杂类型分解为其组成部分,从而分别使用 我们感兴趣的值。

使用模式进行解构是一种便捷的方式,可以彼此独立地使用值的 某些部分,例如结构体中每个字段的值。

忽略模式中的值

你已经看到,有时在模式中忽略值很有用,比如在 match 的最后 一个分支中,用作一个实际上不执行任何操作但能覆盖所有剩余 可能值的兜底。在模式中有几种方法可以忽略整个值或值的一部分: 使用 _ 模式(你已见过)、在另一个模式内部使用 _ 模式、 使用以下划线开头的名称,或使用 .. 忽略值的其余部分。 让我们探讨如何以及为什么要使用这些模式。

使用 _ 忽略整个值

我们一直使用下划线作为通配符模式,它能匹配任何值但不 绑定到该值。这在 match 表达式的最后一个分支中特别有用, 但我们也可以在任何模式中使用它,包括函数参数,如 示例 19-17 所示。

Filename: src/main.rs
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {y}");
}

fn main() {
    foo(3, 4);
}
示例 19-17:在函数签名中使用 _

这段代码将完全忽略作为第一个参数传入的值 3,并打印 This code only uses the y parameter: 4。

在大多数情况下,当你不再需要某个函数参数时,你会修改 签名使其不包含未使用的参数。但忽略函数参数在某些情况下 尤其有用,例如当你实现 trait 时需要特定的类型签名,但 你实现的函数体不需要其中某个参数。这样你就可以避免 编译器对未使用函数参数的警告——如果使用名称的话就会 收到警告。

使用嵌套的 _ 忽略值的一部分

我们也可以在另一个模式内部使用 _ 来忽略值的一部分, 例如当我们只想测试值的一部分、但想在相应代码中不使用 其他部分时。示例 19-18 展示了负责管理某项设置值的代码。 业务需求是:不应允许用户覆盖已有的自定义设置值,但 可以取消该设置,并在当前未设置时为其赋值。

fn main() {
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }

    println!("setting is {setting_value:?}");
}
示例 19-18:在匹配 Some 变体的模式中使用下划线,当我们不需要使用 Some 内的值时

这段代码将打印 Can't overwrite an existing customized value, 然后打印 setting is Some(5)。在第一个 match 分支中,我们 不需要匹配或使用任一 Some 变体内部的值,但确实需要 测试 setting_value 和 new_setting_value 都是 Some 变体 的情况。在这种情况下,我们打印不更改 setting_value 的原因,并且它不会被更改。

在所有其他情况下(如果 setting_value 或 new_setting_value 是 None),由第二个分支中的 _ 模式表示,我们希望 允许 new_setting_value 变为 setting_value。

我们也可以在一个模式中的多个位置使用下划线来忽略特定的 值。示例 19-19 展示了一个忽略五元素元组中第二个和第四个 值的例子。

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

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {first}, {third}, {fifth}");
        }
    }
}
示例 19-19:忽略元组的多个部分

这段代码将打印 Some numbers: 2, 8, 32,值 4 和 16 将被忽略。

通过在名称前加 _ 忽略未使用的变量

如果你创建了一个变量但没有任何地方使用它,Rust 通常会发出 警告,因为未使用的变量可能是一个 bug。然而,有时候创建 一个你暂时不会使用的变量是有用的,比如你在做原型设计或 刚开始一个项目时。在这种情况下,你可以通过让变量名以 下划线开头来告诉 Rust 不要发出未使用变量的警告。在示例 19-20 中,我们创建了两个未使用的变量,但编译这段代码时, 我们只会得到关于其中一个变量的警告。

Filename: src/main.rs
fn main() {
    let _x = 5;
    let y = 10;
}
示例 19-20:在变量名前加下划线以避免未使用变量的警告

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

注意,仅使用 _ 和使用以下划线开头的名称之间存在微妙的 区别。_x 语法仍然会将值绑定到变量,而 _ 则根本不绑定。 为了展示这种区别的重要性,示例 19-21 将给我们一个错误。

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_s) = s {
        println!("found a string");
    }

    println!("{s:?}");
}
示例 19-21:以下划线开头的未使用变量仍然会绑定值,这可能会取得值的所有权

我们会收到一个错误,因为 s 值仍会被移动到 _s 中, 这会阻止我们再次使用 s。但是,单独使用下划线永远不会 绑定到值。示例 19-22 可以正常编译,因为 s 没有被 移动到 _。

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string");
    }

    println!("{s:?}");
}
示例 19-22:使用下划线不会绑定值

这段代码运行良好,因为我们从未将 s 绑定到任何东西; 它没有被移动。

使用 .. 忽略值的剩余部分

对于有许多部分的值,我们可以使用 .. 语法来只使用特定的 部分并忽略其余部分,从而避免为每个被忽略的值都列出下划线。 .. 模式忽略值中我们没有在模式其余部分明确匹配的任何 部分。在示例 19-23 中,我们有一个 Point 结构体,持有 三维空间中的坐标。在 match 表达式中,我们想只对 x 坐标进行操作,并忽略 y 和 z 字段中的值。

fn main() {
    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

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

    match origin {
        Point { x, .. } => println!("x is {x}"),
    }
}
示例 19-23:使用 .. 忽略 Point 中除 x 以外的所有字段

我们列出 x 值,然后只包含 .. 模式。这比必须列出 y: _ 和 z: _ 更快捷,特别是当我们处理有很多字段的 结构体、而只有一两个字段相关时。

.. 语法将根据需要扩展到任意多的值。示例 19-24 展示了 如何在元组中使用 ..。

Filename: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}
示例 19-24:仅匹配元组的第一个和最后一个值,忽略所有其他值

在这段代码中,第一个和最后一个值由 first 和 last 匹配。.. 会匹配并忽略中间的所有内容。

但是,使用 .. 必须是无歧义的。如果不清楚哪些值 要匹配、哪些值应忽略,Rust 会给我们一个错误。示例 19-25 展示了一个歧义使用 .. 的例子,因此它无法编译。

Filename: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {second}")
        },
    }
}
示例 19-25:试图以歧义方式使用 ..

当我们编译这个例子时,会得到如下错误:

$ cargo run
   Compiling patterns v0.1.0 (file:///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 守卫添加条件

match 守卫(match guard)是在 match 分支中模式之后指定的 额外 if 条件,必须也匹配该条件才会选中该分支。match 守卫 对于表达比模式本身更复杂的逻辑很有用。但要注意,它们仅 在 match 表达式中可用,在 if let 或 while let 表达式中不可用。

该条件可以使用模式中创建的变量。示例 19-26 展示了一个 match, 其第一个分支具有模式 Some(x),并且还有一个 match 守卫 if x % 2 == 0(当数字为偶数时为 true)。

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
        Some(x) => println!("The number {x} is odd"),
        None => (),
    }
}
示例 19-26:为模式添加 match 守卫

这个例子将打印 The number 4 is even。当 num 与第一个 分支中的模式比较时,它匹配,因为 Some(4) 匹配 Some(x)。然后 match 守卫检查 x 除以 2 的余数 是否等于 0,由于它是 0,所以选中了第一个分支。

如果 num 是 Some(5),那么第一个分支中的 match 守卫将是 false,因为 5 除以 2 的余数是 1,不等于 0。 Rust 就会进入第二个分支,第二个分支会匹配,因为第二个 分支没有 match 守卫,因此匹配任何 Some 变体。

无法在模式内表达 if x % 2 == 0 这种条件,所以 match 守卫让我们能够表达这种逻辑。这种额外表达能力的缺点是 编译器在涉及 match 守卫表达式时不会尝试检查穷尽性。

在讨论示例 19-11 时,我们提到过可以使用 match 守卫 来解决模式遮蔽问题。回想一下,我们在 match 表达式中 的模式内创建了一个新变量,而不是使用 match 外部的 变量。这个新变量意味着我们无法针对外部变量的值进行 测试。示例 19-27 展示了如何使用 match 守卫来解决 这个问题。

Filename: src/main.rs
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {n}"),
        _ => println!("Default case, x = {x:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");
}
示例 19-27:使用 match 守卫测试与外部变量的相等性

这段代码现在将打印 Default case, x = Some(5)。第二个 match 分支中的模式不会引入一个遮蔽外部 y 的新变量 y,这意味着我们可以在 match 守卫中使用外部的 y。 我们不指定模式 Some(y)(那会遮蔽外部的 y), 而是指定 Some(n)。这创建了一个新变量 n,它不会 遮蔽任何东西,因为 match 外部没有 n 变量。

match 守卫 if n == y 不是一个模式,因此不会引入新 变量。这个 y 就是外部的 y,而不是遮蔽它的新 y。 通过将 n 与 y 进行比较,我们可以寻找与外部 y 具有相同值的值。

你也可以在 match 守卫中使用或运算符 | 来指定多个 模式;match 守卫条件将应用于所有模式。示例 19-28 展示了将使用 | 的模式与 match 守卫结合时的优先级。 本例的关键是 if y 这个 match 守卫同时应用于 4、 5 和 6,尽管看起来 if y 可能只应用于 6。

fn main() {
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}
示例 19-28:将多个模式与 match 守卫结合

match 条件表明:仅当 x 的值等于 4、5 或 6 并且 y 为 true 时,该分支才会匹配。当这段代码运行时, 第一个分支的模式匹配,因为 x 是 4,但 match 守卫 if y 为 false,所以第一个分支未被选中。代码继续到 第二个分支,该分支匹配,程序打印 no。原因在于 if 条件应用于整个模式 4 | 5 | 6,而不仅仅是最后一个值 6。换句话说,match 守卫相对于模式的优先级 表现如下:

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

而不是这样:

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

运行代码后,优先级行为就显而易见了:如果 match 守卫只 应用于使用 | 运算符指定的值列表中的最后一个值,那么 该分支会匹配,程序就会打印 yes。

使用 @ 绑定

at 运算符 @ 让我们能够创建一个变量来持有一个值, 同时测试该值是否匹配某个模式。在示例 19-29 中,我们想测试 Message::Hello 的 id 字段是否在 3..=7 范围内。 我们也希望将该值绑定到变量 id,以便在与该分支关联 的代码中使用它。

fn main() {
    enum Message {
        Hello { id: i32 },
    }

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

    match msg {
        Message::Hello { id: id @ 3..=7 } => {
            println!("Found an id in range: {id}")
        }
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {id}"),
    }
}
示例 19-29:使用 @ 在测试模式的同时绑定到值

这个例子将打印 Found an id in range: 5。通过在范围 3..=7 之前指定 id @,我们将匹配该范围的任何值捕获到 名为 id 的变量中,同时也测试该值是否匹配范围模式。

在第二个分支中,我们在模式中只指定了范围,与该分支 关联的代码没有包含 id 字段实际值的变量。id 字段 的值可能是 10、11 或 12,但与该模式配套的代码不知道 是哪一个。模式代码无法使用 id 字段的值,因为我们 没有将 id 值保存到变量中。

在最后一个分支中,我们指定了一个没有范围的变量, 这样我们就有了可以在分支代码中使用的值,存储在名为 id 的变量中。原因是我们使用了结构体字段简写语法。 但我们没有像前两个分支那样对 id 字段中的值应用 任何测试:任何值都会匹配这个模式。

使用 @ 让我们能在一个模式中测试值并将其保存到变量中。

总结

Rust 的模式在区分不同类型的数据时非常有用。在 match 表达式中使用时,Rust 会确保你的模式覆盖所有可能的值, 否则你的程序将无法编译。let 语句和函数参数中的模式 使这些结构更加有用,可以将值解构为更小的部分,并将 这些部分赋给变量。我们可以根据需要创建简单或复杂的 模式。

接下来,作为本书倒数第二章,我们将讨论 Rust 各种功能的 一些高级方面。