可反驳性:模式是否可能无法匹配

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

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

通常,你不必担心可反驳和不可反驳模式之间的区别;然而,你需要熟悉可反驳性的概念,这样当你在错误消息中看到它时能够做出回应。在这种情况下,你需要根据代码的预期行为,更改模式或你正在使用的模式构造。

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

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value;
}
Listing 19-8: Attempting to use a refutable pattern with let

如果 some_option_value 是一个 None 值,它将无法匹配模式 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/ch18-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 正确地产生了编译器错误。

如果我们有一个在需要不可反驳模式的地方使用的可反驳模式,我们可以通过更改使用该模式的代码来修复:而不是使用let,我们可以使用if let。这样,如果模式不匹配,代码将跳过大括号内的代码,从而能够继续有效执行。列表 19-9 展示了如何修复列表 19-8 中的代码。

fn main() {
    let some_option_value: Option<i32> = None;
    if let Some(x) = some_option_value {
        println!("{x}");
    }
}
Listing 19-9: Using if let and a block with refutable patterns instead of let

我们给代码留了一条出路!这段代码现在完全有效。然而,如果我们给 if let 一个不可拒绝的模式(一个总是会匹配的模式),比如 x,如清单 19-10 所示,编译器会发出警告。

fn main() {
    if let x = 5 {
        println!("{x}");
    };
}
Listing 19-10: Attempting to use an irrefutable pattern with if let

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

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

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