# 模式语法

在本节中,我们将汇集所有在模式中有效的语法,并讨论何时以及为什么使用每一种语法。

# 匹配字面量

正如你在第六章中看到的,你可以直接将模式与字面量进行匹配。以下代码给出了一些示例:

fn main() {
    let x = 1;

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

这段代码打印“one”,因为 x 中的值是 1。这种语法在你希望代码在接收到特定的具体值时采取某个操作时非常有用。

# 匹配命名变量

命名变量是不可反驳的模式,能够匹配任何值,我们在本文档中已经多次使用它们。然而,在使用命名变量时,如果在matchif letwhile let表达式中使用,它会遇到一些复杂情况。因为每一种表达式都会启动一个新的作用域,所以在表达式内部作为模式的一部分声明的变量会隐藏外部同名的变量,这与所有变量的行为相同。在示例 19-11 中,我们声明了一个名为 x 的变量,其值为Some(5),以及一个名为 y 的变量,其值为 10。然后我们创建了一个基于 x 值的match表达式。请查看match语句中的模式和最后的println!,并在运行代码或继续阅读之前,尝试猜测这段代码会打印什么内容。

文件名: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 绑定到Some内部的值。这个值是 5,因此该分支的表达式会执行并打印Matched, y = 5

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

match表达式结束时,它的作用域也结束了,内部的 y 的作用域也结束了。最后的println!输出:x = Some(5), y = 10

为了创建一个比较外部 x 和 y 值的match表达式,而不是引入一个新的变量来遮蔽现有的 y 变量,我们需要使用match guard条件来代替。稍后我们会在“使用 Match Guard 的额外条件”中讨论match guard

# 多重模式

你可以使用|语法来匹配多个模式,它是模式或(OR)运算符。例如,在以下代码中,我们将 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)和数值类型,所以范围只允许与数值或字符类型一起使用。

下面是一个使用字符值范围的例子:

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

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

Rust 可以判断字符'c'位于第一个模式的范围内,并打印出早期的 ASCII 字母。

# 解构以拆分值

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

# 解构结构体

示例 19-12 展示了一个包含两个字段xyPoint结构体,我们可以使用带有let语句的模式来拆解它。

文件名: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:将结构体字段解构为独立变量

这段代码创建了变量ab,它们匹配p结构体中xy字段的值。这个例子展示了,模式中变量的名称不必与结构体的字段名称匹配。然而,通常情况下会将变量名与字段名匹配,以便更容易记住哪些变量来自哪些字段。由于这种常见的用法,并且因为写let Point { x: x, y: y } = p;会有很多重复,Rust 提供了一个简写方式来匹配结构体字段:你只需要列出结构体字段的名称,模式中创建的变量将具有相同的名称。示例 19-13 与示例 19-12 中的代码行为相同,但在let模式中创建的变量是xy,而不是ab

文件名: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:使用结构体字段简写解构结构体字段

这段代码创建了变量xy,它们匹配p变量的xy字段。结果是,变量xy包含来自p结构体的值。

我们还可以将字面值与结构体模式一起解构,而不是为所有字段创建变量。这样可以让我们在解构其他字段时,测试某些字段是否具有特定的值。

在示例 19-14 中,我们有一个match表达式,将Point值分成三种情况:直接位于 x 轴上的点(当y = 0时为真),位于 y 轴上的点(当x = 0时为真),或者既不在 x 轴也不在 y 轴上的点。

文件名: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:在一个模式中解构和匹配字面值

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

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

在这个例子中,p的值通过x字段包含0而匹配了第二个分支,因此这段代码会打印On the y axis at 7

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

# 解构枚举

我们在本文档中已经解构过枚举(例如示例 6-5),但我们还没有明确讨论过,解构枚举的模式对应于枚举中存储的数据定义方式。作为例子,在示例 19-15 中,我们使用了示例 6-2 中的Message枚举,并编写了一个match表达式,使用模式解构每个内部值。

文件名: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 所示。

文件名: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,并且打印出“这段代码只使用了 y 参数: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内部的值时

这段代码会打印“不能覆盖已自定义的值”,然后显示settingSome(5)。在第一个match分支中,我们不需要匹配或使用Some变体中的值,但我们需要测试当setting_valuenew_setting_value都是Some变体的情况。在这种情况下,我们会打印出不更改setting_value的原因,并且它不会被改变。

在所有其他情况下(如果setting_valuenew_setting_valueNone),在第二个分支中通过*模式表达,我们希望允许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 中,我们创建了两个未使用的变量,但当我们编译这段代码时,我们只会收到其中一个变量的警告。

文件名: 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坐标,并忽略yz字段的值。

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 展示了如何在元组中使用..

文件名: src/main.rs:

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

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}

示例 19-24:仅匹配元组中的第一个和最后一个值,忽略中间的所有值

在这段代码中,第一个和最后一个值分别用firstlast匹配。..会匹配并忽略中间的所有值。

然而,使用..必须是明确的。如果不清楚哪些值是用于匹配的,哪些应该被忽略,Rust 会给我们报错。示例 19-25 展示了一个模糊使用..的例子,因此它无法编译。

文件名: 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 来说没有任何特殊含义,因此我们会得到一个编译器错误,因为在两个地方使用..是模糊的。

# 使用匹配守卫的附加条件

匹配守卫是一个附加的if条件,指定在匹配臂的模式之后,只有当该条件也匹配时,才会选择该匹配臂。匹配守卫对于表达比单独的模式更复杂的想法非常有用。但是请注意,匹配守卫仅在match表达式中可用,而在if letwhile let表达式中不可用。

条件可以使用在模式中创建的变量。示例 19-26 展示了一个match,其中第一个匹配臂使用模式Some(x),并且有一个匹配守卫if x % 2 == 0(当数字是偶数时,这个条件为真)。

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:向模式添加匹配守卫

这个例子会打印出“数字 4 是偶数”。当num与第一个匹配臂的模式进行比较时,它匹配,因为Some(4)Some(x)匹配。然后,匹配守卫检查x除以 2 的余数是否等于 0,因为它是 0,所以选择了第一个匹配臂。

如果numSome(5),则第一个匹配臂中的匹配守卫会返回false,因为 5 除以 2 的余数是 1,不等于 0。Rust 会转到第二个匹配臂,第二个匹配臂会匹配,因为它没有匹配守卫,因此匹配任何Some变体。

在模式中无法表达if x % 2 == 0这个条件,因此匹配守卫使我们能够表达这种逻辑。这个额外的表达能力的缺点是,当涉及匹配守卫表达式时,编译器不会尝试检查穷尽性。

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

文件名: 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:使用匹配守卫测试与外部变量的相等性

这段代码现在会打印出“默认情况,x = Some(5)”。第二个匹配臂中的模式并没有引入一个新的变量y来遮蔽外部的y,这意味着我们可以在匹配守卫中使用外部的y。我们没有指定模式为Some(y),因为那样会遮蔽外部的y,而是指定了Some(n)。这创建了一个新的变量n,它没有遮蔽任何变量,因为外部没有n变量。

匹配守卫if n == y并不是模式,因此不会引入新的变量。这个y是外部的y,而不是一个新的y遮蔽了它,我们可以通过比较ny来查找与外部y具有相同值的值。

你也可以在匹配守卫中使用|操作符来指定多个模式;匹配守卫条件将应用于所有模式。示例 19-28 展示了在将使用|的模式与匹配守卫结合时的优先级。这个例子中重要的一点是,if y匹配守卫适用于 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:将多个模式与匹配守卫结合

匹配条件表示,只有当x的值等于 4、5 或 6,并且ytrue时,匹配臂才会匹配。当这段代码运行时,第一个匹配臂的模式匹配成功,因为x是 4,但匹配守卫if yfalse,所以第一个匹配臂没有被选择。代码继续执行到第二个匹配臂,第二个匹配臂成功匹配,程序打印出no。原因是if条件应用于整个模式4 | 5 | 6,而不仅仅是最后一个值6。换句话说,匹配守卫与模式的优先级关系是这样的:

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

而不是这样:

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

运行代码后,优先级行为是显而易见的:如果匹配守卫仅应用于使用|操作符指定的值列表中的最后一个值,则匹配臂会匹配,程序将打印出yes

# @绑定

@操作符让我们在测试值是否匹配某个模式的同时创建一个变量来保存该值。在示例 19-29 中,我们想要测试Message::Helloid字段是否在范围3..=7内。同时,我们还希望将该值绑定到变量id_variable,以便在与匹配臂相关的代码中使用它。我们可以将这个变量命名为id,与字段名相同,但在这个例子中,我们将使用不同的名字。

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

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

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Found an id in range: {id_variable}"),
        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_variable @,我们既捕获了匹配该范围的值,又测试了该值是否匹配该范围模式。

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

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

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

# 总结

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

接下来,在本文档的倒数第二章,我们将探讨 Rust 特性中的一些高级内容。