可证伪性:某个模式有无可能匹配失败

Refutability: Whether a Pattern Might Fail to Match

模式有两种形式:可证伪与不可证伪的。将匹配所传递的任何可能值模式,即为 不可证伪的,irrefuatable。一个示例即为 let x = 5; 语句中的 x;由于 x 会匹配任何值,而因此必定不会匹配失败。那些对某些可能的值,会匹配失败的模式,便是 可证伪的,refutable。这样的一个示例,便是表达式 if let Some(x) = a_value 中的 Some(x),因为若 a_value 中的值为 None 而非 Some 时,这个 Some(x) 模式就将不匹配。

函数参数、let 语句及 for 循环,就只能接受不可证伪的模式,这是由于这些情况下,当值不匹配时,程序便无法执行任何有意义的事情。if letwhile let 表达式,接受可证伪与不可证伪的模式,但由于根据定义,他们被计划来处理可能的匹配失败:某个条件的功能,便在于其根据匹配成功或失败,而区别执行的能力,因此编译器会就不可证伪模式发出告警。

一般来说,咱们无须担心可证伪与不可证伪模式的区别;但是,咱们确实需要熟悉可证伪性的概念,这样咱们在看到报错消息时,就可以予以响应。在这些情形下,咱们将需要根据代码所预期的行为,而要么修改模式,或者修改该模式下用到的那个结构。

接下来就看看,当咱们在 Rust 要求使用不可证伪模式的地方,尝试使用某种可证伪模式,及反过来 Rust 要求使用可证伪模式,而尝试使用不可证伪模式时,会发生什么。下面清单 18-8 给出了一个 let 语句,不过咱们指定了一个可证伪的 Some(x) 模式。正如你会期待的那样,此代码将不会编译。

#![allow(unused)]
fn main() {
    let Some(x) = some_option_value;
}

清单 18-:在 let 下使用可证伪模式的尝试*

some_option_value 是个 None 值,他就会与模式 Some(x) 匹配失败,意味着该模式为可证伪的。但是,由于此处没有可处理 None 值的有效代码,因此该 let 表达式就只能接收某个不可证伪的模式。在编译时,Rust 将抱怨说咱们曾于要求不可证伪模式的某处,使用了可证伪模式:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding: `None` not covered
   --> 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: `Option<i32>` defined here
    = note: the matched value is of type `Option<i32>`
help: you might want to use `if let` to ignore the variant that isn't matched
    |
3   |     let x = if let Some(x) = some_option_value { x } else { todo!() };
    |     ++++++++++                                 ++++++++++++++++++++++

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` due to previous error

由于咱们不曾以 Some(x) 涵盖(且无法涵盖到!)所以有效值,Rust 便理所当然地产生了一个编译器报错。

而当咱们在需要不可证伪模式处,有着某个可证伪模式时,咱们可通过修改用到该模式的代码修复之:与其使用 let,咱们可以使用 if let。随后在该模式不匹配时,该代码就仅仅会跳过位于那花括号中代码,从而给了其有效继续的一种方式。下面清单 18-9 给出了修复清单 18-8 中代码的方式。

#![allow(unused)]
fn main() {
    if let Some(x) = some_option_value {
        println! ("{}", x);
    }
}

清单 18-9:使用 if let 与带有可证伪模式代码块,而非 let

咱们就已给了代码一条出路了!这段代码是完全有效的,尽管其意味着咱们在不接收到报错下,无法使用某个不可证伪模式。而在咱们给到 if let 某个将始终匹配的模式时,如下清单 18-10 中所示,编译器就会给出一条告警。

#![allow(unused)]
fn main() {
    if let x = 5 {
        println! ("{}", x);
    }
}

清单 18-10:在 if let 下使用不可证伪模式的尝试

Rust 会抱怨,以某个不可证伪模式使用 if let 没有意义:

$ cargo run                                                                                                                lennyp@vm-manjaro
   Compiling refutable_demo v0.1.0 (/home/lennyp/rust-lang/refutable_demo)
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: `refutable_demo` (bin "refutable_demo") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/refutable_demo`
5

由于这个原因,除了应以一个不可证伪模式匹配全部剩余值的最后支臂外,其他那些匹配支臂,就必须使用可证伪模式。Rust 允许咱们在仅有一个支臂的 match 中,使用不可证伪模式,但这种语法不是特别有用,并可以一个更简单的 let 语句替换。

现在,咱们就知道了哪些地方要使用模式,以及可证伪与不可证伪模式的区别,下面就来介绍所有可用于创建模式的语法。

Last change: 2023-12-01, commit: 0339beb