可反驳性:模式是否会匹配失败

模式有两种形式:可反驳的(refutable)和不可反驳的(irrefutable)。能够匹配任何可能传递的值的模式是不可反驳的。例如,let x = 5; 语句中的 x 就是不可反驳的,因为 x 可以匹配任何值,因此不会匹配失败。而对于某些可能的值会匹配失败的模式是可反驳的。例如,if let Some(x) = a_value 表达式中的 Some(x) 就是可反驳的,因为如果 a_value 变量中的值是 None 而不是 Some,那么 Some(x) 模式将不会匹配。

函数参数、let 语句和 for 循环只能接受不可反驳的模式,因为当值不匹配时,程序无法做任何有意义的事情。if letwhile let 表达式以及 let...else 语句可以接受可反驳和不可反驳的模式,但编译器会对不可反驳的模式发出警告,因为它们的目的是处理可能的失败:条件语句的功能在于它能够根据成功或失败执行不同的操作。

一般来说,你不需要担心可反驳和不可反驳模式之间的区别;然而,你需要熟悉可反驳性的概念,以便在错误信息中看到它时能够做出反应。在这些情况下,你需要根据代码的预期行为更改模式或使用模式的结构。

让我们来看一个例子,看看当我们尝试在 Rust 要求不可反驳模式的地方使用可反驳模式时会发生什么,反之亦然。Listing 19-8 展示了一个 let 语句,但对于模式,我们指定了 Some(x),这是一个可反驳的模式。正如你所料,这段代码将无法编译。

fn main() { let some_option_value: Option<i32> = None; let Some(x) = some_option_value; }

如果 some_option_valueNone 值,它将无法匹配 Some(x) 模式,这意味着该模式是可反驳的。然而,let 语句只能接受不可反驳的模式,因为代码无法对 None 值做任何有效的操作。在编译时,Rust 会抱怨我们试图在需要不可反驳模式的地方使用可反驳模式:

$ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0005]: refutable pattern in local binding --> src/main.rs:3:9 | 3 | let Some(x) = some_option_value; | ^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option<i32>` help: you might want to use `let else` to handle the variant that isn't matched | 3 | let Some(x) = some_option_value else { todo!() }; | ++++++++++++++++ For more information about this error, try `rustc --explain E0005`. error: could not compile `patterns` (bin "patterns") due to 1 previous error

因为我们没有(也无法!)用 Some(x) 模式覆盖所有可能的值,Rust 正确地生成了一个编译错误。

如果我们在需要不可反驳模式的地方使用了可反驳模式,我们可以通过更改使用模式的代码来修复它:我们可以使用 if let 而不是 let。然后,如果模式不匹配,代码将跳过花括号中的代码,从而使其能够继续有效执行。Listing 19-9 展示了如何修复 Listing 19-8 中的代码。

fn main() { let some_option_value: Option<i32> = None; let Some(x) = some_option_value else { return; }; }

我们为代码提供了一个出路!这段代码现在完全有效。然而,如果我们给 if let 提供一个不可反驳的模式(一个总是会匹配的模式),例如 x,如 Listing 19-10 所示,编译器会发出警告。

fn main() { let x = 5 else { return; }; }

Rust 抱怨说,使用 if let 与不可反驳模式没有意义:

$ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) warning: irrefutable `if let` pattern --> src/main.rs:2:8 | 2 | if let x = 5 { | ^^^^^^^^^ | = note: this pattern will always match, so the `if let` is useless = help: consider replacing the `if let` with a `let` = note: `#[warn(irrefutable_let_patterns)]` on by default warning: `patterns` (bin "patterns") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s Running `target/debug/patterns` 5

因此,match 分支必须使用可反驳的模式,除了最后一个分支,它应该使用不可反驳的模式来匹配所有剩余的值。Rust 允许我们在只有一个分支的 match 中使用不可反驳的模式,但这种语法并不特别有用,可以用更简单的 let 语句代替。

现在你已经知道了在哪里使用模式以及可反驳和不可反驳模式之间的区别,让我们来介绍所有可以用来创建模式的语法。