模式匹配的基本使用
可在如下幾種情況下使用模式匹配:
- let變量賦值
- 函數參數傳值時的模式匹配
- match分支
- if let
- while let
- for迭代的模式匹配
let變量賦值時的模式匹配
let變量賦值時的模式匹配:
#![allow(unused)] fn main() { let PATTERN = EXPRESSION; }
變量是一種最簡單的模式,變量名位於Pattern位置,賦值時的過程:將表達式與模式進行比較匹配,並將任何模式中找到的變量名進行對應的賦值。
例如:
#![allow(unused)] fn main() { let x = 5; let (x, y) = (1, 2); }
第一條語句,變量x是一個模式,在執行該語句時,將表達式5賦值給找到的變量名x。變量賦值總是可以匹配成功。
第二條語句,將表達式(1,2)
和模式(x,y)
進行匹配,匹配成功,於是為找到的變量x和y進行賦值:x=1,y=2
。
如果模式中的元素數量和表達式中返回的元素數量不同,則匹配失敗,編譯將無法通過。
#![allow(unused)] fn main() { let (x,y,z) = (1,2); // 失敗 }
函數參數傳值時的模式匹配
為函數參數傳值和使用let變量賦值是類似的,本質都是在做模式匹配的操作。
例如:
#![allow(unused)] fn main() { fn f1(i: i32){ // xxx } fn f2(&(x, y): &(i32, i32)){ // yyy } }
函數f1
的參數i
就是模式,當調用f1(88)
時,88是表達式,將賦值給找到的變量名i。
函數f2
的參數&(x,y)
是模式,調用f2( &(2,8) )
時,將表達式&(2,8)
與模式&(x,y)
進行匹配,併為找到的變量名x和y進行賦值:x=2,y=8
。
match分支匹配
match分支匹配的用法非常靈活,此處只做基本的用法介紹,後文還會繼續深入其用法。
它的語法為:
#![allow(unused)] fn main() { match VALUE { PATTERN1 => EXPRESSION1, PATTERN2 => EXPRESSION2, PATTERN3 => EXPRESSION3, } }
其中=>
左邊的是各分支的模式,VALUE將與這些分支逐一進行匹配,=>
右邊的是各分支匹配成功後執行的代碼。每個分支後使用逗號分隔各分支,最後一個分支的結尾逗號可以省略(但建議加上)。
match會從前先後匹配各分支,一旦匹配成功則不再繼續向下匹配。
例如:
#![allow(unused)] fn main() { let x = (11, 22); match x { (22, a) => println!("(22, {})", a), // 匹配失敗 (a, b) => println!("({}, {})", a, b), // 匹配成功,停止匹配 (a, 11) => println!("({}, 11)", a), // 匹配失敗 } }
如果某分支對應的要執行的代碼只有一行,則直接編寫該行代碼,如果要執行的代碼有多行,則需加上大括號包圍這些代碼。無論加不加大括號,每個分支都是一個獨立的作用域。
因此,上述match的語法可衍生為如下兩種語法:
#![allow(unused)] fn main() { match VALUE { PATTERN1 => code1, PATTERN2 => code2, PATTERN3 => code3, } match VALUE { PATTERN1 => { code line 1 clod line 2 }, PATTERN2 => { code line 1 clod line 2 }, PATTERN3 => code1, } }
另外,match結構自身也是表達式,它有返回值,且可以賦值給變量。match的返回值由每個分支最後執行的那行代碼決定。Rust要求match的每個分支返回值類型必須相同,且如果是一個單獨的match表達式而不是賦值給變量時,每個分支必須返回()
類型。
例如:
#![allow(unused)] fn main() { let x = (11,22); // 正確,match沒有賦值給變量,分支必須返回Unit值() match x { (a, b) => println!("{}, {}", a, b), // 返回Unit值() // 其他正確寫法:{println!("{}, {}", a, b);}, // 錯誤寫法: println!("{}, {}", a, b);, } // 正確,每個分支都返回Unit值() match x { (a,11) => println!("{}", a), // 該分支匹配失敗 (a,b) => println!("{}, {}", a, b), // 將匹配該分支 } // match返回值賦值給變量,每個分支必須返回相同的類型:i32 let y = match x { (a,11) => { println!("{}", a); a // 該分支的返回值:i32類型 }, (a,b) => { println!("{}, {}", a, b); a + b // 該分支的返回值:i32類型 }, }; }
match也經常用來窮舉Enum類型的所有成員。此時要求窮盡所有成員,如果有遺漏成員,編譯將失敗。可以將_
作為最後一個分支的PATTERN,它將匹配剩餘所有成員。
enum Direction { Up, Down, Left, Right, } fn main(){ let dir = match Direction::Down { Direction::Up => 1, Direction::Down => 2, Direction::Right => 3, _ => 4, }; println!("{}", dir); }
if let
if let
是match的一種特殊情況的語法糖:當只關心一個match分支,其餘情況全部由_
負責匹配時,可以將其改寫為更精簡if let
語法。
#![allow(unused)] fn main() { if let PATTERN = EXPRESSION { // xxx } }
這表示將EXPRESSION的返回值與PATTERN模式進行匹配,如果匹配成功,則為找到的變量進行賦值,這些變量在大括號作用域內有效。如果匹配失敗,則不執行大括號中的代碼。
例如:
#![allow(unused)] fn main() { let x = (11, 22); // 匹配成功,因此執行大括號內的代碼 // if let是獨立作用域,變量a b只在大括號中有效 if let (a, b) = x { println!("{},{}", a, b); } // 等價於如下代碼 let x = (11, 22); match x { (a, b) => println!("{},{}", a, b), _ => (), } }
if let
可以結合else if
、else if let
和else
一起使用。
#![allow(unused)] fn main() { if let PATTERN = EXPRESSION { // XXX } else if { // YYY } else if let PATTERN = EXPRESSION { // zzz } else { // zzzzz } }
這時候它們和match多分支類似。但實際上有很大的不同:使用match分支匹配時,要求分支之間是有關聯(例如枚舉類型的各個成員)且窮盡的,但Rust編譯器不會檢查if let
的模式之間是否有關聯關係,也不檢查if let
是否窮盡所有可能情況,因此,即使在邏輯上有錯誤,Rust也不會給出編譯錯誤提醒。
例如,下面是一個使用了if let..else if let
的示例,該示例窮舉了Enum類型的所有成員,還包括該枚舉類型之外的情況,但即使去掉任何一個分支,也都不會報錯。
enum Direction { Up, Down, Left, Right, } fn main() { let dir = Direction::Down; if let Direction::Left = dir { println!("Left"); } else if let Direction::Right = dir { println!("Right"); } else { println!("Up or Down or wrong"); } }
while let
只要while let
的模式匹配成功,就會一直執行while循環內的代碼。
例如:
#![allow(unused)] fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } }
當stack.pop
成功時,將匹配Some(top)
成功,並將pop返回的值賦值給top,當沒有元素可pop時,返回None,匹配失敗,於是while循環退出。
for迭代
for迭代也有模式匹配的過程:為控制變量賦值。例如:
#![allow(unused)] fn main() { let v = vec!['a','b','c']; for (idx, value) in v.iter().enumerate(){ println!("{}: {}", idx, value); } }