Rust – 函數與控制流
主題:模式匹配(match)
簡介
在 Rust 中,模式匹配是語言最核心的控制流工具之一。它不僅能取代傳統的 if‑else 鏈,還能在編譯期為我們檢查 所有可能的分支 是否被完整覆蓋,從而大幅降低執行時錯誤的機會。對於處理列舉(enum)、解構資料結構、甚至是錯誤處理(Result、Option),match 都提供了既簡潔又安全的寫法。
本篇文章將從 概念、實作範例、常見陷阱、最佳實踐,一路帶領讀者深入了解 match,並說明它在真實專案中的應用情境。即使你是剛接觸 Rust 的新手,也能在閱讀完後,立即在自己的程式碼中使用 match 來寫出更具可讀性與安全性的程式。
核心概念
1. match 的基本語法
match <expression> {
<pattern1> => <result1>,
<pattern2> => <result2>,
// ...
_ => <default_result>, // 「_」是通配符,匹配所有未被列出的情況
}
<expression>:要被匹配的值,通常是變數、函式回傳值或是更複雜的運算式。<pattern>:描述「如果值符合什麼樣的形狀」的模式。模式可以是字面量、變數、解構、或是更高階的 守衛(if條件)。=>:左側模式匹配成功時執行右側的表達式或程式區塊。_:通配符,代表「所有其他情況」。若省略_,編譯器會要求你列舉所有可能的分支,否則會產生錯誤。
重點:
match必須是 exhaustive(完整),即所有可能的值都必須被處理。這是 Rust 保證程式安全的關鍵機制。
2. 匹配字面量與範圍
let grade = 85;
let remark = match grade {
90..=100 => "優秀",
80..=89 => "良好",
70..=79 => "普通",
0..=69 => "需要加油",
_ => "分數不合法", // 超出 0~100 的情況
};
println!("成績評語:{}", remark);
90..=100為 閉區間(包含兩端),用來匹配連續的數值。- 若使用
..(開區間)則不包含右端點,例如0..10匹配 0~9。
3. 解構列舉(Enum)與結構體(Struct)
enum Message {
Quit,
Echo(String),
Move { x: i32, y: i32 },
ChangeColor(i32, i32, i32),
}
fn handle(msg: Message) {
match msg {
Message::Quit => println!("程式結束"),
Message::Echo(text) => println!("回音:{}", text),
Message::Move { x, y } => println!("移動到 ({}, {})", x, y),
Message::ChangeColor(r, g, b) => {
println!("顏色變為 RGB({},{},{})", r, g, b);
}
}
}
Message::Echo(text)直接把內部的String解構為變數text。Message::Move { x, y }以 結構體解構語法 取得欄位。Message::ChangeColor(r, g, b)以 元組解構 取得三個顏色分量。
4. 使用守衛(Guard)增強匹配條件
fn describe(num: i32) -> &'static str {
match num {
n if n % 2 == 0 => "偶數",
n if n % 2 != 0 => "奇數",
_ => "未知", // 事實上永遠不會到這裡
}
}
if後面的條件稱為 守衛,允許在同一個模式上加入額外的布林判斷。- 守衛必須返回
bool,且只能使用已在模式中綁定的變數(如上例的n)。
5. match 作為表達式(Expression)
match 不僅是控制流語句,還可以直接作為 值 回傳,這在函式回傳、變數賦值時非常便利。
let number = 7;
let parity = match number % 2 {
0 => "even",
1 => "odd",
_ => unreachable!(), // 這裡永遠不會執行
};
println!("{} is {}", number, parity);
match的每個分支都必須回傳相同型別(本例皆為&str),否則編譯錯誤。
6. if let 與 while let 的簡化寫法
當只關心 單一 分支時,if let 可以讓程式碼更簡潔:
let opt = Some(42);
if let Some(value) = opt {
println!("取得值:{}", value);
} else {
println!("沒有值");
}
if let其實是match的語法糖,只匹配一個模式,其他情況走else。while let同理,用於迭代Option、Result或自訂迭代器的情況。
程式碼範例(實用示例)
以下提供 五個 常見且實務導向的範例,每個範例都附有說明與註解。
範例 1:解析命令列參數
enum Cmd {
Help,
Version,
Add(i32, i32),
Sub(i32, i32),
}
fn parse(args: &[String]) -> Cmd {
match args {
// 只輸入程式名稱,顯示說明
[_] => Cmd::Help,
// `--version` 旗標
[_, flag] if flag == "--version" => Cmd::Version,
// `add 3 5`
[_, cmd, a, b] if cmd == "add" => {
let x = a.parse::<i32>().unwrap_or(0);
let y = b.parse::<i32>().unwrap_or(0);
Cmd::Add(x, y)
}
// `sub 10 4`
[_, cmd, a, b] if cmd == "sub" => {
let x = a.parse::<i32>().unwrap_or(0);
let y = b.parse::<i32>().unwrap_or(0);
Cmd::Sub(x, y)
}
// 其他情況回傳說明
_ => Cmd::Help,
}
}
說明:透過
match結合 守衛(if)與 切片模式([_, cmd, a, b]),可以一次性處理多種指令格式,程式碼比連續的if‑else更具可讀性。
範例 2:錯誤處理(Result)的鏈結
use std::fs::File;
use std::io::{self, Read};
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
let result = read_file("data.txt");
match result {
Ok(text) => println!("檔案內容:\n{}", text),
Err(e) => eprintln!("讀取失敗:{}", e),
}
}
說明:
Result本身就是一個枚舉,使用match可以清楚分辨成功與失敗的路徑,並在失敗時提供完整的錯誤資訊。
範例 3:計算樹狀結構(AST)之遞迴評估
enum Expr {
Number(i64),
Add(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
}
fn eval(expr: &Expr) -> i64 {
match expr {
Expr::Number(n) => *n,
Expr::Add(lhs, rhs) => eval(lhs) + eval(rhs),
Expr::Mul(lhs, rhs) => eval(lhs) * eval(rhs),
}
}
fn main() {
// (2 + 3) * 4
let ast = Expr::Mul(
Box::new(Expr::Add(Box::new(Expr::Number(2)), Box::new(Expr::Number(3)))),
Box::new(Expr::Number(4)),
);
println!("結果 = {}", eval(&ast)); // => 20
}
說明:透過
match直接對每種 AST 節點解構,遞迴呼叫eval,程式結構清晰且不需要額外的if判斷。
範例 4:使用 match 取代多層 if‑else 的狀態機
enum State {
Idle,
Running(i32), // 內含計數器
Paused,
Stopped,
}
fn next(state: State, event: char) -> State {
match (state, event) {
(State::Idle, 's') => State::Running(0),
(State::Running(cnt), 'p') => State::Paused,
(State::Running(cnt), 't') => State::Running(cnt + 1),
(State::Paused, 'r') => State::Running(0),
(_, 'x') => State::Stopped,
(s, _) => s, // 其他事件保持原狀態
}
}
說明:把 狀態 與 事件 同時作為模式
(state, event),一次性描述所有轉移規則,避免繁瑣的match嵌套或if連鎖。
範例 5:解構 Option 與 Result 的混合情況
fn combine(a: Option<i32>, b: Result<i32, &'static str>) -> Option<i32> {
match (a, b) {
(Some(x), Ok(y)) => Some(x + y),
(Some(_), Err(_)) => None, // 任一錯誤返回 None
(None, _) => None,
(_, Err(_)) => None,
}
}
說明:把兩個不同型別的列舉同時解構成 元組模式,讓邏輯一目了然。此技巧在處理多個可能失敗的來源時非常實用。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 建議的做法 |
|---|---|---|
| 忘記處理所有分支 | 編譯器會報錯 non-exhaustive patterns,但有時使用 _ 會隱藏潛在問題。 |
儘量列舉所有具體情況,僅在真的不需要關注時才使用 _。 |
| 模式變數遮蔽 | 在同一個 match 中重複使用相同變數名稱會遮蔽外層變數,導致意外行為。 |
為每個分支使用獨特的變數名稱,或使用 _ 表示「不需要」的值。 |
| 守衛過度使用 | 複雜的 if 守衛會讓模式難以閱讀,失去 match 的簡潔優勢。 |
若守衛條件過長,考慮將其抽成函式或改用 if let/while let。 |
忘記 break/return |
match 本身是表達式,不需要 break;但在 loop 中使用 match 時,忘記 break 會造成無限迴圈。 |
在需要提前結束迴圈時,明確寫 break 或 return。 |
使用 unreachable!() 不當 |
把 unreachable!() 放在理論上不會執行的分支,若未來列舉新增變種,會在執行時 panic。 |
改用 _ => unreachable!() 前先確保列舉已被 #[non_exhaustive] 標記,或直接處理新變種。 |
最佳實踐
盡量讓
match完全覆蓋
完整性是 Rust 保證安全的根本。列舉新增變種時,編譯器會提醒你更新所有match,這正是防止遺漏的機制。使用具體模式取代通配符
具體模式(如Message::Quit)讓程式碼更具可讀性,也能讓 IDE 提供更好的自動完成與跳轉。保持分支簡潔
每個分支的程式碼不宜過長。若需要多行邏輯,建議抽成函式,再在match中呼叫。善用模式解構
直接在match中解構結構體或列舉,能減少中間變數,提升可讀性。在需要「只關心一個分支」時使用
if letif let可以讓程式更簡短,同時仍保留match的安全性檢查。
實際應用場景
| 場景 | 為何使用 match |
|---|---|
| 命令列工具 | 解析子指令、旗標與參數時,match 能一次性處理多種組合,避免繁瑣的 if‑else。 |
| 網路協定解析 | 協定封包往往是多種型別的組合(Header、Payload、Error),使用 match 解構每個欄位,可確保所有可能的封包類型都被處理。 |
| 狀態機(FSM) | 把 當前狀態 與 輸入事件 同時作為模式,讓所有轉移規則集中於一個 match,易於維護與測試。 |
| 錯誤傳遞 | Result<T, E> 與 Option<T> 的組合在 I/O、資料庫、Web 框架中屢見不鮮,match 能清晰分離成功與失敗路徑。 |
| 抽象語法樹(AST)評估 | 編譯器或解譯器的核心往往是遞迴遍歷 AST,match 能直接對每種節點類型做出相應的計算或轉換。 |
範例:在一個簡易的 HTTP 伺服器中,根據請求方法(GET、POST、PUT、DELETE)使用
match直接分派處理函式,既避免了大量if request.method == ...的重複,又能在新增方法時得到編譯器提醒。
總結
match是 Rust 最具表現力的控制流結構,提供 完整性檢查、模式解構與 守衛 三大特性。- 透過 字面量、範圍、列舉、結構體、元組 等多樣模式,我們可以在單一語句內完成複雜的分支邏輯。
match同時也是 表達式,可直接作為值回傳,讓程式碼更具函式式風格。- 常見的陷阱包括忘記列舉所有分支、過度使用守衛以及不當使用
_。遵守 完整性、具體化、保持分支簡潔 的最佳實踐,能寫出安全且易讀的程式。 - 在實務上,
match廣泛應用於 CLI 解析、協定解碼、狀態機、錯誤處理與 AST 評估 等領域,是每位 Rust 開發者不可或缺的工具。
掌握 match 後,你將能以更宣告式、安全的方式撰寫 Rust 程式,從而在大型專案中保持程式碼的可維護性與可靠性。祝你在 Rust 的旅程中玩得開心,寫出更優雅的程式!