可反駁性:何時模式可能會配對失敗

模式有兩種形式:可反駁的(refutable)與不可反駁的(irrefutable)。可以配對任何可能數值的模式屬於不可反駁的(irrefutable)。其中一個例子就是陳述式 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 要求不可反駁模式的地方使用可反駁模式的範例與其反例。範例 18-8 顯示了一個 let 陳述式,但是我們指定的模式是 Some(x),這是可反駁模式。如我們所預期的,此程式碼無法編譯。

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

範例 18-8:嘗試在 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: `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 的程式碼。

fn main() {
    let some_option_value: Option<i32> = None;
    if let Some(x) = some_option_value {
        println!("{}", x);
    }
}

範例 18-9:使用 if let 而非 let 來使用可反駁模式

我們給了程式碼出路!此程式碼可以完美執行,雖然這也代表我們使用不可反駁模式的話會得到一些警告。如果我們給予 if let 一個像是 x 這樣永遠能配對的模式的話,編譯器會出現警告,如範例 18-10 所示。

fn main() {
    if let x = 5 {
        println!("{}", x);
    };
}

範例 18-10:嘗試在 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: `#[warn(irrefutable_let_patterns)]` on by default
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`

warning: `patterns` (bin "patterns") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5

基於此原因,match 的分支必須是可反駁模式。除了最後一個分支因為要配對任何剩餘數值,所以會是不可反駁模式。Rust 允許我們在 match 只使用一個不可反駁模式的分支,不過這樣做並不是很實用,且可以直接用簡單的 let 陳述式取代。

現在你知道哪裡能使用模式,以及可反駁與不可反駁模式的不同了。讓我們來涵蓋模式建立時可以使用的所有語法吧。