Rust 從入門到精通09-模式解構(gòu)
"Pattern Destructure" 是 Rust 中一個(gè)重要且實(shí)用的設(shè)計(jì)。通常翻譯為 “模式解構(gòu)”。
Destructure 單詞翻譯為把原來的結(jié)構(gòu)肢解為單獨(dú)的、原始的部分。
下面我們舉例說明什么是模式解構(gòu):
fn?main()?{
????let?tul?=?(1,"a",false);
????let?(head,center,tail)?=?tul;
????println!("{}",head);//1
????println!("{}",center);//a
????println!("{}",tail);//false
}
第2行代碼是構(gòu)造;第3行代碼是拆解,產(chǎn)生了三個(gè)新的變量。
Rust 的模式解構(gòu)不僅出現(xiàn)在 let 語句中,還可以出現(xiàn)在 match、if let、while let、函數(shù)調(diào)用、閉包調(diào)用等情景中。
1、match
1.1 用于匹配多種取值
rustenum?Direction{
????East,
????West,
????South,
????North,
}
fn?print(x:Direction){
????match?x?{
????????Direction::East?=>?{
????????????println!("East");
????????}
????????Direction::West?=>?{
????????????println!("West");
????????}
????????Direction::South?=>?{
????????????println!("South");
????????}
????????Direction::North?=>?{
????????????println!("North");
????????}
????}
}
當(dāng)一個(gè)類型有多種取值時(shí),特別適合適用 match 表達(dá)式??梢缘葍r(jià)于 if-else
1.2 必須匹配到每個(gè)分支exhaustive
exhuastive:徹底的,詳盡的
對(duì)應(yīng)到 match 表達(dá)式上,Rust 要求 match 需要對(duì)所有的情況做徹底的,無遺漏的匹配。比如對(duì)于上面的例子,我們刪除一個(gè)匹配分支(East)
注意:編譯器會(huì)保證 match 所有分支合起來一定覆蓋了所有的情況,但是不保證各個(gè)分支重復(fù)的問題。
enum?Direction{
????East,
????West,
????South,
????North,
}
fn?print(x:Direction){
????match?x?{
????????Direction::West?=>?{
????????????println!("West");
????????}
????????Direction::South?=>?{
????????????println!("South");
????????}
????????Direction::North?=>?{
????????????println!("North");
????????}
????}
}
fn?main()?{
????print(Direction::West);
}
編譯就會(huì)報(bào)錯(cuò):

1.3 一個(gè)分支匹配多種類型 |
我們可以通過 | 在一個(gè)分支匹配多種情況;
enum?Direction{
????East,
????West,
????South,
????North,
}
fn?print(x:Direction){
????match?x?{
????????Direction::East?=>?{
????????????println!("East");
????????}
????????Direction::West?|?Direction::South??|?Direction::North=>?{
????????????println!("Other");
????????}
????}
}
1.3 通配模式和 _ 占位符
有時(shí)候,我們并不知道到底有多少種匹配情況,這時(shí)候可能需要一個(gè)類似通配符,來匹配所有沒有考慮的情況,從而避免編譯失敗。
fn?print(x:Direction){
????match?x?{
????????Direction::West?=>?{
????????????println!("West");
????????}
????????Direction::South?=>?{
????????????println!("South");
????????}
????????other?=>?{
????????????println!("orther");
????????}
????}
}
上面代碼前面兩個(gè)分支我們匹配了 West 和 South,剩下兩個(gè)分支我們直接定義了一個(gè)名為 other 的變量(這個(gè)變量可以任意命名),它將匹配所有未被特殊列出來的值。
需要注意:我們必須將通配分支放在最后,因?yàn)槟J绞前错樞蚱ヅ涞?。如果我們?cè)谕ㄅ浞种Ш筇砑悠渌种?,Rust 將會(huì)警告我們,因?yàn)榇撕蟮姆种в肋h(yuǎn)不會(huì)被匹配到。
除此之外,Rust 還推出了"_" 占位符來匹配所有情況。
enum?Direction{
????East,
????West,
????South,
????North,
}
fn?print(x:Direction){
????match?x?{
????????Direction::East?=>?{
????????????println!("East");
????????}
????????_?=>?{
????????????println!("other");
????????}
????}
}
1.4 忽略匹配占位符 _
下劃線除了用作 match 表達(dá)式中的分支額外匹配之外,還可以用來表示一個(gè)占位符,表示雖然匹配到了,但是忽略掉。
struct?P(i32,i32,i32);
fn?add(p:P)?->?i32{
????let?P(x,_,y)?=?p;
????x+y
}
fn?main()?{
????let?p?=?P(1,2,3);
????println!("{}",add(p));//4
}
我們只想求元祖結(jié)構(gòu)體P的第一個(gè)和末尾數(shù)據(jù)之和,所以中間的數(shù)據(jù)可以通過_ 忽略掉。
1.5 match guards 匹配看守
match 和 if 配合一起使用。
enum?OptionalInt{
????Value(i32),
????Missing,
}
fn?match_if(op:OptionalInt){
????match?op?{
????????OptionalInt::Value(i)?if?i?>?10?=>?{println!("big?int")}
????????OptionalInt::Missing?=>?println!("not?int")
????}
}
但是上面這段代碼會(huì)編譯錯(cuò)誤,提示沒有覆蓋所有的情況。(如果移除 if 條件,是正常編譯的)

那么如何修改這個(gè)問題呢?我們?cè)黾?if 匹配條件看看:
fn?match_if(op:OptionalInt){
????match?op?{
????????OptionalInt::Value(i)?if?i?>?10?=>?{println!("big?int")},
????????OptionalInt::Value(i)?if?i<=?10?=>?{println!("get?int")},
????????OptionalInt::Missing?=>?println!("not?int"),
????}
}
發(fā)現(xiàn)還是編譯失敗。
我們已經(jīng)通過 if 覆蓋了所有的情況,但是還是不能編譯通過,這其實(shí)也是 rust 編譯器目前無法做到的,它畢竟不是一個(gè)完整的數(shù)學(xué)解析器。
這種情況下,我們加個(gè)下劃線匹配就可以了。
fn?match_if(op:OptionalInt){
????match?op?{
????????OptionalInt::Value(i)?if?i?>?10?=>?{println!("big?int")},
????????OptionalInt::Value(i)?if?i<=?10?=>?{println!("get?int")},
????????_?=>?println!("沒有特殊意義"),
????????OptionalInt::Missing?=>?println!("not?int"),
????}
}
2、if-let
比如有一個(gè)類型為 Option的變量 val,我們要取出里面的值。
①、使用match表達(dá)式
fn?getOptionVal(str:Option<String>)?->?String{
????match?str?{
????????Some(x)?=>?x,
????????None?=>?panic!("called?`Option::unwrap()`?on?a?`None`?value")
????}
}
fn?main()?{
????let?val?=?Some("ab".into());
????println!("{}",getOptionVal(val));//ab
}
②、if 表達(dá)式
fn?getOptionVal(str:Option<String>)?->?String{
????if?str.is_some(){
????????return?str.unwrap();
????}
????panic!("called?`Option::unwrap()`?on?a?`None`?value")
}
fn?main()?{
????let?val?=?Some("ab".into());
????println!("{}",getOptionVal(val));//ab
}
③、if-let
fn?getOptionVal(str:Option<String>)?->?String{
????if?let?Some(x)?=?str{
????????return?x;
????}
????panic!("called?`Option::unwrap()`?on?a?`None`?value")
}
fn?main()?{
????let?val?=?Some("ab".into());
????println!("{}",getOptionVal(val));//ab
}
第二種方法(if 表達(dá)式)取值分兩步,第一步調(diào)用 is_some()判斷,第二步調(diào)用unwrap()取值;
而if-let 看上去就簡潔很多,但是和 if 表達(dá)式相比并沒有效率上的差別,只是rust提供的一個(gè)語法糖。