W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
ch06-02-match.md
commit c76f1b4d011fe59fc4f5e6f258070fc40d9921e4
Rust 有一個叫做 match
的極為強大的控制流運算符,它允許我們將一個值與一系列的模式相比較,并根據(jù)相匹配的模式執(zhí)行相應代碼。模式可由字面值、變量、通配符和許多其他內容構成;第十八章會涉及到所有不同種類的模式以及它們的作用。match
的力量來源于模式的表現(xiàn)力以及編譯器檢查,它確保了所有可能的情況都得到處理。
可以把 match
表達式想象成某種硬幣分類器:硬幣滑入有著不同大小孔洞的軌道,每一個硬幣都會掉入符合它大小的孔洞。同樣地,值也會通過 match
的每一個模式,并且在遇到第一個 “符合” 的模式時,值會進入相關聯(lián)的代碼塊并在執(zhí)行中被使用。
因為剛剛提到了硬幣,讓我們用它們來作為一個使用 match
的例子!我們可以編寫一個函數(shù)來獲取一個未知的硬幣,并以一種類似驗鈔機的方式,確定它是何種硬幣并返回它的美分值,如示例 6-3 中所示。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
示例 6-3:一個枚舉和一個以枚舉成員作為模式的 match
表達式
拆開 value_in_cents
函數(shù)中的 match
來看。首先,我們列出 match
關鍵字后跟一個表達式,在這個例子中是 coin
的值。這看起來非常像 if
使用的表達式,不過這里有一個非常大的區(qū)別:對于 if
,表達式必須返回一個布爾值,而這里它可以是任何類型的。例子中的 coin
的類型是示例 6-3 中定義的 Coin
枚舉。
接下來是 match
的分支。一個分支有兩個部分:一個模式和一些代碼。第一個分支的模式是值 Coin::Penny
而之后的 =>
運算符將模式和將要運行的代碼分開。這里的代碼就僅僅是值 1
。每一個分支之間使用逗號分隔。
當 match
表達式執(zhí)行時,它將結果值按順序與每一個分支的模式相比較。如果模式匹配了這個值,這個模式相關聯(lián)的代碼將被執(zhí)行。如果模式并不匹配這個值,將繼續(xù)執(zhí)行下一個分支,非常類似一個硬幣分類器??梢該碛腥我舛嗟姆种В菏纠?6-3 中的 match
有四個分支。
每個分支相關聯(lián)的代碼是一個表達式,而表達式的結果值將作為整個 match
表達式的返回值。
如果分支代碼較短的話通常不使用大括號,正如示例 6-3 中的每個分支都只是返回一個值。如果想要在分支中運行多行代碼,可以使用大括號,而分支后的逗號是可選的。例如,如下代碼在每次使用Coin::Penny
調用時都會打印出 “Lucky penny!”,同時仍然返回代碼塊最后的值,1
:
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
匹配分支的另一個有用的功能是可以綁定匹配的模式的部分值。這也就是如何從枚舉成員中提取值的。
作為一個例子,讓我們修改枚舉的一個成員來存放數(shù)據(jù)。1999 年到 2008 年間,美國在 25 美分的硬幣的一側為 50 個州的每一個都印刷了不同的設計。其他的硬幣都沒有這種區(qū)分州的設計,所以只有這些 25 美分硬幣有特殊的價值??梢詫⑦@些信息加入我們的 enum
,通過改變 Quarter
成員來包含一個 State
值,示例 6-4 中完成了這些修改:
#[derive(Debug)] // 這樣可以立刻看到州的名稱
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
示例 6-4:Quarter
成員也存放了一個 UsState
值的 Coin
枚舉
想象一下我們的一個朋友嘗試收集所有 50 個州的 25 美分硬幣。在根據(jù)硬幣類型分類零錢的同時,也可以報告出每個 25 美分硬幣所對應的州名稱,這樣如果我們的朋友沒有的話,他可以將其加入收藏。
在這些代碼的匹配表達式中,我們在匹配 Coin::Quarter
成員的分支的模式中增加了一個叫做 state
的變量。當匹配到 Coin::Quarter
時,變量 state
將會綁定 25 美分硬幣所對應州的值。接著在那個分支的代碼中使用 state
,如下:
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
如果調用 value_in_cents(Coin::Quarter(UsState::Alaska))
,coin
將是 Coin::Quarter(UsState::Alaska)
。當將值與每個分支相比較時,沒有分支會匹配,直到遇到 Coin::Quarter(state)
。這時,state
綁定的將會是值 UsState::Alaska
。接著就可以在 println!
表達式中使用這個綁定了,像這樣就可以獲取 Coin
枚舉的 Quarter
成員中內部的州的值。
我們在之前的部分中使用 Option<T>
時,是為了從 Some
中取出其內部的 T
值;我們還可以像處理 Coin
枚舉那樣使用 match
處理 Option<T>
!只不過這回比較的不再是硬幣,而是 Option<T>
的成員,但 match
表達式的工作方式保持不變。
比如我們想要編寫一個函數(shù),它獲取一個 Option<i32>
,如果其中含有一個值,將其加一。如果其中沒有值,函數(shù)應該返回 None
值,而不嘗試執(zhí)行任何操作。
得益于 match
,編寫這個函數(shù)非常簡單,它將看起來像示例 6-5 中這樣:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
示例 6-5:一個在 Option<i32>
上使用 match
表達式的函數(shù)
讓我們更仔細地檢查 plus_one
的第一行操作。當調用 plus_one(five)
時,plus_one
函數(shù)體中的 x
將會是值 Some(5)
。接著將其與每個分支比較。
None => None,
值 Some(5)
并不匹配模式 None
,所以繼續(xù)進行下一個分支。
Some(i) => Some(i + 1),
Some(5)
與 Some(i)
匹配嗎?當然匹配!它們是相同的成員。i
綁定了 Some
中包含的值,所以 i
的值是 5
。接著匹配分支的代碼被執(zhí)行,所以我們將 i
的值加一并返回一個含有值 6
的新 Some
。
接著考慮下示例 6-5 中 plus_one
的第二個調用,這里 x
是 None
。我們進入 match
并與第一個分支相比較。
None => None,
匹配上了!這里沒有值來加一,所以程序結束并返回 =>
右側的值 None
,因為第一個分支就匹配到了,其他的分支將不再比較。
將 match
與枚舉相結合在很多場景中都是有用的。你會在 Rust 代碼中看到很多這樣的模式:match
一個枚舉,綁定其中的值到一個變量,接著根據(jù)其值執(zhí)行代碼。這在一開始有點復雜,不過一旦習慣了,你會希望所有語言都擁有它!這一直是用戶的最愛。
match
還有另一方面需要討論:這些分支必須覆蓋了所有的可能性。考慮一下 plus_one
函數(shù)的這個版本,它有一個 bug 并不能編譯:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
我們沒有處理 None
的情況,所以這些代碼會造成一個 bug。幸運的是,這是一個 Rust 知道如何處理的 bug。如果嘗試編譯這段代碼,會得到這個錯誤:
$ cargo run
Compiling enums v0.1.0 (file:///projects/enums)
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:15
|
3 | match x {
| ^ pattern `None` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Option<i32>`
For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums` due to previous error
Rust 知道我們沒有覆蓋所有可能的情況甚至知道哪些模式被忘記了!Rust 中的匹配是 窮盡的(exhaustive):必須窮舉到最后的可能性來使代碼有效。特別的在這個 Option<T>
的例子中,Rust 防止我們忘記明確的處理 None
的情況,這讓我們免于假設擁有一個實際上為空的值,從而使之前提到的價值億萬的錯誤不可能發(fā)生。
讓我們看一個例子,我們希望對一些特定的值采取特殊操作,而對其他的值采取默認操作。想象我們正在玩一個游戲,如果你擲出骰子的值為 3,角色不會移動,而是會得到一頂新奇的帽子。如果你擲出了 7,你的角色將失去新奇的帽子。對于其他的數(shù)值,你的角色會在棋盤上移動相應的格子。這是一個實現(xiàn)了上述邏輯的 match
,骰子的結果是硬編碼而不是一個隨機值,其他的邏輯部分使用了沒有函數(shù)體的函數(shù)來表示,實現(xiàn)它們超出了本例的范圍:
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
對于前兩個分支,匹配模式是字面值 3 和 7,最后一個分支則涵蓋了所有其他可能的值,模式是我們命名為 other
的一個變量。other
分支的代碼通過將其傳遞給 move_player
函數(shù)來使用這個變量。
即使我們沒有列出 u8
所有可能的值,這段代碼依然能夠編譯,因為最后一個模式將匹配所有未被特殊列出的值。這種通配模式滿足了 match
必須被窮盡的要求。請注意,我們必須將通配分支放在最后,因為模式是按順序匹配的。如果我們在通配分支后添加其他分支,Rust 將會警告我們,因為此后的分支永遠不會被匹配到。
Rust 還提供了一個模式,當我們不想使用通配模式獲取的值時,請使用 _
,這是一個特殊的模式,可以匹配任意值而不綁定到該值。這告訴 Rust 我們不會使用這個值,所以 Rust 也不會警告我們存在未使用的變量。
讓我們改變游戲規(guī)則:現(xiàn)在,當你擲出的值不是 3 或 7 的時候,你必須再次擲出。這種情況下我們不需要使用這個值,所以我們改動代碼使用 _
來替代變量 other
:
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
這個例子也滿足窮舉性要求,因為我們在最后一個分支中明確地忽略了其他的值。我們沒有忘記處理任何東西。
最后,讓我們再次改變游戲規(guī)則,如果你擲出 3 或 7 以外的值,你的回合將無事發(fā)生。我們可以使用單元值(在“元組類型”一節(jié)中提到的空元組)作為 _
分支的代碼:
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
在這里,我們明確告訴 Rust 我們不會使用與前面模式不匹配的值,并且這種情況下我們不想運行任何代碼。
我們將在第 18 章中介紹更多關于模式和匹配的內容。現(xiàn)在,讓我們繼續(xù)討論 if let
語法,這在 match
表達式有點啰嗦的情況下很有用。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: