本文 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 letwhile 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! 失去錯誤處理的彈性。 改用 ResultOption 回傳錯誤,讓呼叫端決定如何處理。
忘記 break/continueloop match 本身不會自動跳出迴圈。 若需要在匹配後立即結束迴圈,使用 breakreturn

最佳實踐

  1. 保持分支簡潔:每個分支只做一件事,必要時抽成小函式。
  2. 利用 #[must_use]:對返回 ResultOption 的函式加上此屬性,避免忘記處理錯誤。
  3. 盡量使用 if let:當只有單一模式需要處理時,if let 能減少樣板程式。
  4. 使用 matches!:快速檢查某個值是否符合特定模式,語法更簡潔。
let is_error = matches!(result, Err(_));
  1. 善用 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 的旅程中玩得開心、寫得順手! 🚀