本文 AI 產出,尚未審核
Rust 課程 – 列舉與模式匹配
主題:match 進階用法
簡介
在 Rust 中,模式匹配 (match) 是語言最具表現力的控制流之一。它不僅能取代傳統的 if‑else,更能結合列舉 (enum)、結構體、元組以及引用等複雜型別,讓程式碼在安全性與可讀性上同時提升。對於剛接觸 Rust 的學習者而言,掌握 match 的基本語法已足以應付大部分情境;但在實務開發中,往往需要更靈活、更高階的寫法,例如 守衛 (guard)、解構 (destructuring)、模式結合 (or)、以及 if let / while let 等技巧。
本篇文章將從核心概念出發,逐步介紹 match 的進階用法,並提供實用範例、常見陷阱與最佳實踐,最後說明在真實專案中如何運用這些技巧提升程式品質。
核心概念
1. 基本語法回顧
enum Direction {
North,
South,
East,
West,
}
fn describe(dir: Direction) -> &'static str {
match dir {
Direction::North => "向北",
Direction::South => "向南",
Direction::East => "向東",
Direction::West => "向西",
}
}
match必須覆蓋 所有可能的情形,否則編譯器會報錯。- 每個分支的返回值型別必須相同,或能夠自動推斷為同一型別。
2. 解構列舉與結構體
2.1 解構列舉的資料
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn handle(msg: Message) {
match msg {
Message::Quit => println!("結束程式"),
Message::Move { x, y } => println!("移動到 ({}, {})", x, y),
Message::Write(text) => println!("文字: {}", text),
Message::ChangeColor(r, g, b) => println!("顏色變為 RGB({},{},{})", r, g, b),
}
}
- 大括號
{}用於 解構具名欄位,圓括號()用於 解構位置欄位。 - 解構後即可直接使用欄位名稱,提升可讀性。
2.2 解構結構體與元組
struct Point(i32, i32);
struct Rectangle { top_left: Point, bottom_right: Point }
fn area(rect: Rectangle) -> i32 {
match rect {
Rectangle {
top_left: Point(x1, y1),
bottom_right: Point(x2, y2),
} => (x2 - x1).abs() * (y2 - y1).abs(),
}
}
- 透過 嵌套解構,可以一次取得多層資料,避免重複寫
let。
3. 模式守衛(Guard)
當單純的模式不足以區分不同情況時,可在分支後加上 if 條件,稱為 模式守衛。
fn classify(num: i32) -> &'static str {
match num {
n if n < 0 => "負數",
n if n == 0 => "零",
n if n > 0 && n % 2 == 0 => "正偶數",
_ => "正奇數",
}
}
- 守衛允許 在同一個模式 中加入額外判斷,保持程式碼的結構化。
- 注意:守衛只能使用 已綁定的變數(如上例的
n),不能直接使用未綁定的值。
4. 多模式(Or)與範圍匹配
4.1 多模式 (|)
fn is_vowel(c: char) -> bool {
match c {
'a' | 'e' | 'i' | 'o' | 'u' |
'A' | 'E' | 'I' | 'O' | 'U' => true,
_ => false,
}
}
- 使用
|可把 多個相同結果的分支 合併,減少冗長。
4.2 範圍匹配 (..=)
fn grade(score: u8) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
0..=59 => "F",
_ => "無效分數",
}
}
..=表示 閉區間,適合處理連續數值的分類。
5. if let 與 while let:簡化單一分支匹配
當只關心 某一個特定模式 時,使用 if let 可以避免寫完整的 match。
fn print_first_option(opt: Option<i32>) {
if let Some(v) = opt {
println!("第一個值是 {}", v);
} else {
println!("沒有值");
}
}
while let 則常用於 迭代器 或 流式資料:
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("彈出 {}", top);
}
6. 透過 match 取得引用與可變引用
fn modify(vec: &mut Vec<i32>, idx: usize) {
match vec.get_mut(idx) {
Some(v) => *v += 10,
None => println!("索引 {} 超出範圍", idx),
}
}
get_mut回傳Option<&mut T>,使用match可以安全地取得 可變引用,避免手動unwrap帶來的 panic。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 忘記覆蓋所有情形 | 編譯錯誤 non-exhaustive patterns。 |
使用 _ 通配符或 #[non_exhaustive] 列舉,或在 IDE 中啟用 match exhaustive 警示。 |
| 守衛中使用未綁定變數 | 會產生「cannot find value」錯誤。 | 必須先在模式中綁定變數(如 n if n > 0),再於守衛使用。 |
過度巢狀 match |
使程式碼難以閱讀。 | 考慮使用 if let + else if let,或將部分邏輯抽離成函式。 |
在 match 分支中直接 panic! |
失去錯誤處理的彈性。 | 改用 Result 或 Option 回傳錯誤,讓呼叫端決定如何處理。 |
忘記 break/continue 在 loop 中 |
match 本身不會自動跳出迴圈。 |
若需要在匹配後立即結束迴圈,使用 break 或 return。 |
最佳實踐
- 保持分支簡潔:每個分支只做一件事,必要時抽成小函式。
- 利用
#[must_use]:對返回Result或Option的函式加上此屬性,避免忘記處理錯誤。 - 盡量使用
if let:當只有單一模式需要處理時,if let能減少樣板程式。 - 使用
matches!宏:快速檢查某個值是否符合特定模式,語法更簡潔。
let is_error = matches!(result, Err(_));
- 善用
ref/ref mut:在模式中取得引用,避免不必要的所有權搬移。
match &my_vec[0] {
ref v if *v > 10 => println!("大於 10"),
_ => {}
}
實際應用場景
1. 解析命令列參數
enum Cmd {
Add { x: i32, y: i32 },
Sub(i32, i32),
Help,
}
fn parse(args: &[String]) -> Cmd {
match args {
[cmd, a, b] if cmd == "add" => Cmd::Add {
x: a.parse().unwrap(),
y: b.parse().unwrap(),
},
[cmd, a, b] if cmd == "sub" => Cmd::Sub(
a.parse().unwrap(),
b.parse().unwrap(),
),
[_] => Cmd::Help,
_ => Cmd::Help,
}
}
- 透過 多模式 + 守衛,一次完成指令辨識與參數解析。
2. 處理網路協定的狀態機
enum HttpState {
Waiting,
ReceivingHeaders,
ReceivingBody(usize), // 已收到的長度
Completed,
Error(String),
}
fn next(state: HttpState, event: HttpEvent) -> HttpState {
match (state, event) {
(HttpState::Waiting, HttpEvent::Connect) => HttpState::ReceivingHeaders,
(HttpState::ReceivingHeaders, HttpEvent::HeadersComplete) => HttpState::ReceivingBody(0),
(HttpState::ReceivingBody(len), HttpEvent::Data(chunk)) => {
let new_len = len + chunk.len();
if new_len >= EXPECTED_LEN {
HttpState::Completed
} else {
HttpState::ReceivingBody(new_len)
}
}
(_, HttpEvent::Error(msg)) => HttpState::Error(msg),
_ => state, // 其他組合保持原狀態
}
}
- 使用 元組模式 同時匹配 舊狀態 與 事件,寫出清晰的狀態轉移表。
3. 解析 JSON(或類似結構)時的安全解構
use serde_json::Value;
fn extract_user(data: &Value) -> Option<(String, u32)> {
match data {
Value::Object(map) => {
let name = map.get("name")?.as_str()?.to_string();
let age = map.get("age")?.as_u64()? as u32;
Some((name, age))
}
_ => None,
}
}
match搭配?讓錯誤傳遞變得直觀,同時保證 不會因為缺欄位而 panic。
總結
match 是 Rust 中最具表現力的語法之一,透過 解構、守衛、多模式、範圍匹配 等進階技巧,我們可以在保持 安全性(編譯期檢查)與 可讀性(結構化分支)的同時,寫出簡潔且高效的程式碼。
- 解構 讓我們一次取得多層資料,避免繁雜的
let。 - 守衛 為同一模式提供額外條件,提升分支的彈性。
- 多模式 與 範圍匹配 減少重複代碼,讓意圖更明確。
if let/while let為單一模式提供了更簡潔的語法糖。
在實務開發中,善用這些技巧可以快速完成 命令列解析、狀態機實作、資料解構與錯誤處理 等常見任務,同時避免常見的陷阱(未覆蓋所有情形、過度巢狀等),提升程式碼的品質與維護性。
掌握 match 的進階用法,將讓你在 Rust 的生態系中如虎添翼,寫出更安全、更易讀的程式。祝你在 Rust 的旅程中玩得開心、寫得順手! 🚀