模式匹配的基本使用

可在如下幾種情況下使用模式匹配:

  • 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 ifelse if letelse一起使用。


#![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);
}
}