编写软件的很大一部分工作围绕着检查某些数据是否具有特定形状(“模式”),从中提取信息,然后在匹配时作出反应。为了方便这一点,许多现代语言,包括 Rust,都支持所谓的“模式匹配”。
如果您是 Rust 新手或想复习知识,您可以先阅读 Rust Book 中的 第 6 章,“枚举和模式匹配” 和 第 18 章,“模式和匹配”,或者在参考手册中阅读更多关于
match
表达式 和 模式 的内容。
Rust 中的模式匹配工作原理是检查内存中的一个位置(place)(即“数据”)是否与特定模式(pattern)匹配。在这篇文章中,我们将介绍一些近期将在稳定版 Rust 中提供的模式改进,以及一些已经在 Nightly 版本中提供的改进。
如果您熟悉文中讨论的 Nightly 特性并想帮助推动它们稳定,请跳到 *我如何提供帮助?。
[head, tail @ ..]
子切片模式,列表是软件中最基本和常见的数据结构之一。在 Rust 中,列表通常是内存中元素的连续序列,即一个切片(slice)。
由于切片如此普遍,处理它们的重要性不言而喻。为此,我们在 Rust 1.26.0 中稳定了固定长度切片模式。因此,现在可以写诸如 let [a, b, c] = my_array;
来解构一个包含 3 个元素的数组。然而,很多时候我们处理的是长度未知的切片,因此如果只有固定长度切片模式,我们必须提供一个备用的 match
分支,例如使用 _
作为模式。
在 Rust 1.42.0 中,我们正在稳定化子切片模式。为了引入子切片模式,我们使用 ..
来表示一个可变长度的间隙,它匹配 ..
前后模式未匹配到的尽可能多的元素。例如,在解析器中,我们希望在属性列表 attrs
后面没有项时报错,所以我们写
/// Recover if we parsed attributes and expected an item but there was none.
这里我们有两个子切片模式,第一个是 [x0, .., xn]
。在这种情况下,模式绑定了第一个元素 x0
和最后一个元素 xn
,忽略了中间的所有内容,匹配至少包含两个元素的切片。同时,[]
和 [x0]
匹配少于两个元素的情况,所以编译器知道我们已经覆盖了所有可能性。在后一种情况下,我们提取了切片的 penultimate
(倒数第二个)元素,这正如其名称所示,也要求切片至少包含两个元素。
我们还可以将子切片绑定到变量。例如,假设我们希望除函数的最后一个参数外,禁止所有参数中使用 ...
。如果是这样,我们可以写
match &*fn_decl.inputs
在这里,ps @ ..
将切片的初始元素绑定到 ps
并忽略最后一个元素。
在 Nightly 中经过 7 年多的酝酿,经历了许多曲折后,子切片模式终于将稳定。为了实现这一点,我们不得不重新设计该特性,修补借用检查器中的健全性漏洞,并对穷尽性检查器进行大量重构。有关我们如何走到这一步的更多信息,请阅读稳定化报告、Thomas Hartmann 的博客文章,并请关注 3 月 12 日发布的 1.42.0 版本公告。
嵌套 OR 模式
当对一个 enum
进行模式匹配时,某些变体的逻辑可能完全相同。为了避免重复,match
、if let
或 while let
表达式中的 |
分隔符可以用于表示如果任何一个由 |
分隔的模式匹配,就应该执行该分支。例如,我们可以写
// Any local node that may call something in its body block should be explored.
这虽然可用,但 Some(_)
仍然重复了几次。有了 #![feature(or_patterns)]
,该特性最近在 Nightly 中变得可用,可以避免这种重复
// Any local node that may call something in its body block should be explored.
以前,在 match
表达式中使用 |
时,|
语法是 match
本身的一部分。有了 or_patterns
,它现在成了模式本身的一部分,所以你可以任意嵌套 OR 模式,并在 let
语句中使用它们
let Ok | Err = foo;
OR 模式覆盖了所有由 |
连接(“or-ed”)的模式的并集(union)。为了确保无论匹配到哪个候选项,所有绑定都是一致且已初始化的,每个 or-ed 模式必须包含完全相同的绑定集,具有相同的类型和相同的绑定模式。
@
后面的绑定
当匹配某个子结构时,有时你希望保留整个结构。例如,给定 Some(Expr { .. })
,你可能想绑定外层的 Some(_)
层。在 Rust 中,这可以通过使用诸如 expr @ Some(Expr { .. })
来实现,它将匹配到的位置绑定到 expr
,同时确保它匹配 Some(Expr { .. })
。
假设 Expr
也有一个字段 span
你想使用。在过去,也就是 Rust 1.0 之前,这是可能的,但现在,它会导致错误
error: pattern bindings are not allowed after an `@`
-/lib.rs:L:C
|
L | bar @ Some =>
| ^^^^ not allowed after `@`
这在 #16053 中被改为错误,主要是因为在基于旧 AST 的借用检查器中,难以以健全的方式编码借用规则。
从那时起,我们移除了旧的借用检查器,转而使用基于 MIR 的新借用检查器,MIR 是一种更简单、更适合借用检查的数据结构。具体来说,对于像 let ref x @ ref y = a;
这样的语句,我们会得到大致与 let x = &a; let y = &a;
相同的 MIR。
所以现在,将绑定放在 @
右边的情况可以由新的借用检查器统一且正确地处理(例如,编译器不会允许 ref x @ ref mut y
),我们决定在 #![feature(bindings_after_at)]
特性门控下允许这样做,该特性现已在 Nightly 中可用。启用此特性后,你可以写例如
我们希望通过提供此特性,消除语言中一个令人困惑的角落。
ref
绑定
组合 by-move 和 by-出于与 @
后面的绑定类似的原因,Rust 目前不允许将正常的 by-move 绑定与 by-ref
绑定组合使用。例如,如果您写...
... 您将收到一个错误
error: cannot bind by-move and by-ref in the same pattern
-/main.rs:3:10
|
3 | let = tup;
| by-move pattern here
然而,与此同时,编译器却完全允许...
... 即使这些程序之间没有语义上的差异。
现在我们已经切换到新的借用检查器(如前一节所述),我们也在 Nightly 中放宽了这一限制,所以在 #![feature(move_ref_pattern)]
特性门控下,你可以写
我如何提供帮助?
总结一下,我们有三个不稳定的特性,它们以不同的方式改进了模式匹配
#![feature(or_patterns)]
,允许你任意嵌套 or 模式,例如Some(Foo | Bar)
#![feature(bindings_after_at)]
,允许例如ref x @ Some(ref y)
#![feature(move_ref_pattern)]
,允许例如(x, ref y)
,其中x
是 by-move,y
是 by-reference
为了帮助我们将这些特性过渡到稳定的 Rust,我们需要您的帮助以确保它们符合预期的质量标准。要提供帮助,请考虑
- 在适用的地方使用这些特性,如果您可以使用 Nightly 编译器,并将任何错误、问题、诊断不足等作为 issue 进行报告。
- 查看在特性门控标签下报告的问题(例如
F-or_patterns
),看看您是否可以帮助解决其中的任何问题。- 特别是,如果您能帮忙编写测试,将不胜感激。
感谢您的阅读,祝您模式匹配愉快!