模式与匹配
模式是 Auto 中用于匹配类型结构(包括复杂和简单类型)的特殊语法。将模式与 is 表达式和其他构造结合使用,可以让你更好地控制程序的流程。模式由以下元素的某种组合构成:
- 字面量
- 解构的数组、枚举、类型或元组
- 变量
- 通配符
- 占位符
一些模式示例包括 x、(a, 3) 和 Some(Color.Red)。在模式有效的上下文中,这些组成部分描述了数据的形状。然后我们的程序将值与模式进行匹配,以确定它是否具有正确的数据形状来继续运行特定的代码。
要使用模式,我们将其与某个值进行比较。如果模式匹配该值,我们就在代码中使用该值的部分。回顾第 6 章中使用模式的 is 表达式,比如硬币分拣机的例子。如果值符合模式的形状,我们可以使用命名的部分。如果不符合,与该模式关联的代码就不会运行。
本章是关于模式的所有内容的参考。我们将介绍可以使用模式的有效位置、可反驳模式和不可反驳模式之间的区别,以及你可能见过的各种模式语法。到本章结束时,你将知道如何使用模式以清晰的方式表达许多概念。
注意: Auto 使用
is关键字替代 Rust 的match。模式语法几乎完全相同,但关键字不同:value is { ... }而不是match value { ... }。
所有可以使用模式的位置
模式在 Auto 中的许多地方都会出现,而且你在不知不觉中已经大量使用了它们!本节讨论所有模式有效的位置。
is 分支
正如第 6 章所讨论的,我们在 is 表达式的分支中使用模式。正式地说,is 表达式由 is 关键字、要匹配的值以及一个或多个由模式和表达式组成的分支定义,如果值匹配该分支的模式,则运行表达式:
value is {
PATTERN -> EXPRESSION
PATTERN -> EXPRESSION
PATTERN -> EXPRESSION
}例如,以下是第 6 章中匹配 ?int 值的 is 表达式:
is 表达式的一个要求是它们必须是_穷尽的_——所有可能的情况都必须被考虑到。确保覆盖每种可能性的一种方法是在最后一个分支使用捕获全部的模式。
特殊的 _ 模式可以匹配任何值,但永远不会绑定到变量,因此通常用于最后一个分支。
let 语句
每次使用 let 语句时,你都在使用模式。更正式地说,let 语句看起来像这样:
let PATTERN = EXPRESSION这里我们将一个元组与模式进行匹配。Auto 将值 (1, 2, 3) 与模式 (x, y, z) 比较,发现值匹配模式——即两者元素数量相同——所以 Auto 将 1 绑定到 x,2 绑定到 y,3 绑定到 z。
如果模式中的元素数量与元组中的元素数量不匹配,整体类型就不匹配,我们会得到编译错误。
条件 if let 表达式
在第 6 章中,我们讨论了如何使用 if let 表达式作为只匹配一种情况的 is 表达式的简写。
Auto 使用与 Rust 相同的 if let 语法。if let 也可以引入新变量来遮蔽现有变量,就像 Rust 一样。
while let 条件循环
与 if let 类似,while let 条件循环允许 while 循环在模式继续匹配时持续运行:
此示例打印 1、2,然后打印 3。
for 循环
在 for 循环中,紧跟在关键字 for 后面的值是一个模式。代码清单 19-5 演示了如何在 for 循环中使用模式来解构元组。
函数参数
函数参数也是模式。我们可以在函数参数中解构元组:
此代码打印 Current location: (3, 5)。值 &(3, 5) 匹配模式 &(x, y),所以 x 是 3,y 是 5。
可反驳性:模式是否可能匹配失败
模式有两种形式:不可反驳的_和_可反驳的。对于任何传入值都会匹配的模式是_不可反驳的_。例如 let x = 5 中的 x,因为 x 匹配任何值,所以不可能匹配失败。对于某些可能值会匹配失败的模式是_可反驳的_。例如 if let Some(x) = a_value 中的 Some(x),因为如果值是 None,Some(x) 模式就不会匹配。
函数参数、let 语句和 for 循环只能接受不可反驳的模式,因为当值不匹配时程序无法做有意义的事情。if let 和 while let 表达式接受可反驳和不可反驳的模式。
如果我们尝试在需要不可反驳模式的地方使用可反驳模式,编译器会产生错误:
let ... else 形式处理模式不匹配的情况。
模式语法
匹配字面量
此代码打印 one,因为 x 中的值是 1。
匹配命名变量
命名变量是匹配任何值的不可反驳模式。但是,在 is 表达式中使用命名变量时有一个复杂之处。因为 is 开启新的作用域,在表达式中作为模式一部分声明的变量会遮蔽外部同名的变量:
这打印 Matched, y = 5,因为 Some(y) 模式引入了一个新的 y,遮蔽了外部的 y = 10。最后打印 at the end: x = Some(5), y = 10,因为内部 y 的作用域已经结束。
使用 | 匹配多个模式
使用 ..= 匹配范围
如果 x 是 1、2、3、4 或 5,第一个分支将匹配。范围只允许用于数字或 char 值。
解构以拆分值
类型(结构体)
我们还可以在模式中使用字面值进行解构:
这打印 On the y axis at 7,因为 x 是 0。
枚举
这打印 Change color to red 0, green 160, and blue 255。
嵌套解构
在模式中忽略值
使用 _ 忽略整个值
使用嵌套的 _ 忽略部分值
这打印 Some numbers: 2, 8, 32。
使用 _ 前缀标记未使用变量
如果你创建了一个变量但没有使用它,Auto 会发出警告。你可以通过以下划线开头的名称来抑制警告:_x 仍然会绑定值,但 _ 完全不会绑定。这种区别对所有权很重要:_ 不会移动值,但 _x 会。
使用 .. 忽略剩余部分
.. 模式忽略我们没有显式匹配的值的所有部分。它必须是无歧义的——每个模式中只能使用一次 ..。
匹配守卫
_匹配守卫_是在 is 分支的模式之后指定的额外 if 条件,该条件也必须匹配才能选择该分支:
这打印 The number 4 is even。
匹配守卫用于表达模式本身无法表达的更复杂想法。你还可以使用守卫与外部变量进行比较:
@ 绑定
@ 操作符允许我们在测试值是否匹配模式的同时创建一个保存该值的变量:
这打印 Found an id in range: 5。通过在范围 3..=7 之前指定 id @,我们捕获了匹配范围的值,同时也测试了它。
is vs match 速查表
| 功能 | Auto(is) | Rust(match) |
|---|---|---|
| 关键字 | is | match |
| 分支语法 | PATTERN -> EXPR | PATTERN => EXPR, |
| 穷尽性 | 必须 | 必须 |
| 通配符 | _ | _ |
| 多个模式 | 1 | 2 | 1 | 2 |
| 范围 | 1..=5 | 1..=5 |
| 匹配守卫 | if condition | if condition |
| 绑定 | x @ 1..=5 | x @ 1..=5 |
| 忽略剩余 | .. | .. |
| 解构类型 | Type(x, y) | Type { x, y } |
| 解构枚举 | Enum.Variant(x) | Enum::Variant(x) |
总结
Auto 的模式在区分不同类型的数据方面非常有用。当在 is 表达式中使用时,Auto 确保你的模式覆盖了所有可能的值,否则程序不会编译。let 语句和函数参数中的模式使这些构造更加有用,能够将值解构为更小的部分并将这些部分赋给变量。我们可以创建简单或复杂的模式来满足我们的需求。
与 Rust 的主要语法差异是使用 is 而不是 match,以及 -> 而不是 =>。模式语法本身——字面量、变量、通配符、范围、守卫和 @ 绑定——几乎完全相同。
在下一章中,我们将探讨 Auto 的一些高级特性,包括 sys(Auto 的 unsafe 等价物)和编译期元编程。