本文 AI 產出,尚未審核

列舉與模式匹配 — 列舉(Enums)定義


簡介

在 Rust 中,列舉(enum) 是一種強大的代型(type),它允許我們把「同一類型」的多種可能值聚合在一起。與傳統語言的 enum(僅能儲存整數常數)不同,Rust 的列舉可以攜帶資料、支援模式匹配(match)以及結合方法(impl),因此常被用來描述 狀態機、抽象語法樹(AST)或錯誤類型 等需要「多樣形態」的情境。

掌握列舉的定義與使用,能讓程式碼更具表現力、型別安全且易於維護。本文將從語法到實務應用,逐步帶你了解 Rust 列舉的核心概念,並提供常見陷阱與最佳實踐,幫助你在專案中正確、有效地運用列舉。


核心概念

1️⃣ 基本語法

最簡單的列舉僅列出幾個變體(variant),每個變體不帶任何資料:

enum Direction {
    North,
    East,
    South,
    West,
}
  • Direction 成為一個新型別,使用時必須明確指明變體,例如 Direction::North
  • 列舉的每個變體本身就是 常量,可直接比較或作為 match 的分支。
fn move_forward(dir: Direction) {
    match dir {
        Direction::North => println!("向北前進"),
        Direction::East  => println!("向東前進"),
        Direction::South => println!("向南前進"),
        Direction::West  => println!("向西前進"),
    }
}

小技巧:若列舉僅用於表示「開」或「關」之類的二元狀態,仍建議使用 enum 而非 bool,因為語意更清晰且未來擴充更方便。


2️⃣ Tuple‑like 變體(類似元組)

變體可以攜帶 匿名欄位,類似元組的寫法:

enum Message {
    Quit,                       // 不帶資料
    Move { x: i32, y: i32 },    // 結構體風格(下節說明)
    Write(String),              // 單一匿名欄位
    ChangeColor(i32, i32, i32), // 三個 i32
}

使用方式:

fn handle(msg: Message) {
    match msg {
        Message::Quit => println!("收到 Quit 訊息"),
        Message::Write(text) => println!("文字:{}", text),
        Message::ChangeColor(r, g, b) => {
            println!("顏色變成 R:{}, G:{}, B:{}", r, g, b);
        }
        Message::Move { x, y } => {
            println!("移動到 ({}, {})", x, y);
        }
    }
}
  • 匿名欄位 讓每個變體自行決定要攜帶多少、什麼類型的資料,不需要額外的結構體
  • match 中,欄位會自動解構(destructure),使得程式碼簡潔且安全。

3️⃣ Struct‑like 變體(具名欄位)

若想讓變體的欄位具有名稱,可使用 結構體風格

enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { a: f64, b: f64, c: f64 },
}

範例:

fn area(s: Shape) -> f64 {
    match s {
        Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
        Shape::Rectangle { width, height } => width * height,
        Shape::Triangle { a, b, c } => {
            // 海龍公式
            let p = (a + b + c) / 2.0;
            (p * (p - a) * (p - b) * (p - c)).sqrt()
        }
    }
}
  • 具名欄位提供 自說明的結構,在大型列舉中能大幅提升可讀性。
  • 同樣支援模式匹配的解構語法,且欄位名稱可直接使用,避免位置錯誤。

4️⃣ 為列舉實作方法(impl

列舉不只是資料容器,也可以擁有 關聯函式(associated functions)方法(methods)

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    /// 取得下一個燈號
    fn next(&self) -> TrafficLight {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Yellow => TrafficLight::Red,
            TrafficLight::Green => TrafficLight::Yellow,
        }
    }

    /// 判斷是否可以通行
    fn can_go(&self) -> bool {
        matches!(self, TrafficLight::Green)
    }
}

使用:

let mut light = TrafficLight::Red;
println!("現在是 {:?},能否通行? {}", light, light.can_go());
light = light.next();
println!("切換後是 {:?},能否通行? {}", light, light.can_go());
  • 透過 impl,列舉可以封裝行為,保持資料與邏輯的高內聚
  • matches! 巨集是 簡潔的布林判斷,適合只需要檢查變體是否符合某個模式的情況。

5️⃣ 與 OptionResult 的關係

Rust 標準庫中最常見的列舉是 Option<T>Result<T, E>,它們展示了 列舉在錯誤處理與空值表示上的威力

enum Option<T> {
    None,
    Some(T),
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • 這兩個列舉的設計理念,就是 「讓編譯器在編譯期就檢查」,避免忘記處理 NoneErr 的情況。
  • 在自己的程式中,盡量使用列舉取代裸露的錯誤碼或 null,可提升程式安全性與可讀性。

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記在 match 中列舉所有變體 編譯器會報錯,但在大型列舉或未來擴充時容易遺漏。 使用 #[non_exhaustive] 標記列舉,或在 match 最後加上 _ => unreachable!(),同時在 CI 中加強測試。
在變體間混用匿名與具名欄位 會讓程式碼風格不一致,閱讀成本提升。 統一風格:若變體需要多個欄位,建議全部採用具名結構體風格。
過度嵌套列舉 多層列舉會導致匹配過於繁瑣。 考慮使用 struct + enum 組合,或使用 Box<Enum> 以減少遞迴深度。
enum 中放入過大的資料 每個變體的大小會影響整個列舉的記憶體佈局,可能導致不必要的拷貝。 使用 Box<T>Rc<T> 包裝大資料,使列舉大小固定(指向堆上資料)。
忽略 #[derive(Debug, Clone, PartialEq)] 手動實作會很繁瑣,且容易遺漏欄位。 為列舉加上常用的衍生(derive)特徵,提升除錯與測試便利性。

最佳實踐

  1. 以語意命名變體enum HttpStatus { Ok, NotFound, InternalServerError }enum Code { A, B, C } 更具可讀性。
  2. 盡量讓列舉保持 「單一職責」(single responsibility):每個列舉只描述一個概念,例如 enum AuthError 只處理認證錯誤。
  3. 使用 match 而非 if let:除非只關心單一變體,否則 match 能保證 完整性檢查
  4. 善用 matches! 巨集:簡化只需布林結果的判斷,如 if matches!(state, State::Running)
  5. 在公共 API 中使用 #[non_exhaustive]:防止外部程式碼依賴全部變體,保留未來擴充的彈性。

實際應用場景

1️⃣ 狀態機(Finite State Machine)

在網路協議、遊戲開發或 UI 流程中,常需要描述「當前狀態」與「可遷移的事件」:

enum ConnectionState {
    Disconnected,
    Connecting { retries: u8 },
    Connected { peer: String },
    Error(String),
}

透過 match 可以清晰地寫出每個狀態的處理邏輯,且編譯器會提醒你若忘記處理某個狀態。

2️⃣ 抽象語法樹(AST)

編譯器或解譯器會把程式碼解析成樹狀結構,列舉是描述節點類型的理想選擇:

enum Expr {
    Literal(i64),
    Binary {
        left: Box<Expr>,
        op: BinOp,
        right: Box<Expr>,
    },
    Unary {
        op: UnOp,
        expr: Box<Expr>,
    },
}

每個變體都攜帶不同的子樹,使用 Box 讓遞迴結構在編譯期保持固定大小。

3️⃣ 錯誤類別(Domain‑specific Error)

自訂錯誤列舉可以讓呼叫端以 型別安全 的方式處理不同錯誤:

#[derive(Debug)]
enum ConfigError {
    NotFound,
    ParseError(String),
    PermissionDenied,
}

搭配 Result<T, ConfigError>,呼叫者必須顯式處理每種錯誤,避免隱藏失敗。

4️⃣ UI 元件的屬性

在 GUI 框架中,常用列舉描述元件的不同屬性或事件:

enum ButtonEvent {
    Click,
    HoverEnter,
    HoverLeave,
    Disabled,
}

事件分派器只需要 match event { ... } 即可完成所有行為的路由。


總結

  • 列舉是 Rust 中最具表現力的代型,它不僅能列出多種變體,還能在每個變體上攜帶資料、實作方法,並與模式匹配 (match) 完美結合。
  • 基本語法Tuple‑likeStruct‑like方法實作,本文示範了 5 個常見範例,讓你快速上手。
  • 常見陷阱(如遺漏變體、過大資料、過度嵌套)與 最佳實踐(語意命名、單一職責、完整匹配)提供了實務上避免錯誤的指引。
  • 狀態機、AST、錯誤處理、UI 事件 等真實情境中,列舉都是不可或缺的工具,能提升程式的可讀性、型別安全與未來可擴充性。

掌握列舉的定義與使用,你就能在 Rust 專案中寫出 更安全、更清晰、更具彈性的程式碼。祝你玩得開心,寫出優雅的 Rust 程式!