可反駁性:何時模式可能會配對失敗
模式有兩種形式:可反駁的(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 let
與 while let
表達式接受可反駁與不可反駁的模式,但是編譯器會警告不可反駁的模式,因為定義上來說它們用來處理可能會失敗的場合,條件表達式的功能就是依據成功或失敗來執行不同動作。
大致上來說,你通常不需要擔心可反駁與不可反駁模式之間的區別,不過你會需要熟悉可反駁性這樣的概念,所以當你看到錯誤訊息時,能及時反應理解。在這樣的場合,你需要依據程式碼的預期行為來改變模式或是使用模式的結構。
讓我們看看當我們嘗試在 Rust 要求不可反駁模式的地方使用可反駁模式的範例與其反例。範例 18-8 顯示了一個 let
陳述式,但是我們指定的模式是 Some(x)
,這是可反駁模式。如我們所預期的,此程式碼無法編譯。
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value;
}
如果 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); } }
我們給了程式碼出路!此程式碼可以完美執行,雖然這也代表我們使用不可反駁模式的話會得到一些警告。如果我們給予 if let
一個像是 x
這樣永遠能配對的模式的話,編譯器會出現警告,如範例 18-10 所示。
fn main() { if let x = 5 { println!("{}", x); }; }
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
陳述式取代。
現在你知道哪裡能使用模式,以及可反駁與不可反駁模式的不同了。讓我們來涵蓋模式建立時可以使用的所有語法吧。