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

模式有兩種形式:可反駁的(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 陳述式取代。

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