列舉與模式匹配 — 列舉(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️⃣ 與 Option、Result 的關係
Rust 標準庫中最常見的列舉是 Option<T> 與 Result<T, E>,它們展示了 列舉在錯誤處理與空值表示上的威力:
enum Option<T> {
None,
Some(T),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
- 這兩個列舉的設計理念,就是 「讓編譯器在編譯期就檢查」,避免忘記處理
None或Err的情況。 - 在自己的程式中,盡量使用列舉取代裸露的錯誤碼或
null,可提升程式安全性與可讀性。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記在 match 中列舉所有變體 |
編譯器會報錯,但在大型列舉或未來擴充時容易遺漏。 | 使用 #[non_exhaustive] 標記列舉,或在 match 最後加上 _ => unreachable!(),同時在 CI 中加強測試。 |
| 在變體間混用匿名與具名欄位 | 會讓程式碼風格不一致,閱讀成本提升。 | 統一風格:若變體需要多個欄位,建議全部採用具名結構體風格。 |
| 過度嵌套列舉 | 多層列舉會導致匹配過於繁瑣。 | 考慮使用 struct + enum 組合,或使用 Box<Enum> 以減少遞迴深度。 |
在 enum 中放入過大的資料 |
每個變體的大小會影響整個列舉的記憶體佈局,可能導致不必要的拷貝。 | 使用 Box<T> 或 Rc<T> 包裝大資料,使列舉大小固定(指向堆上資料)。 |
忽略 #[derive(Debug, Clone, PartialEq)] |
手動實作會很繁瑣,且容易遺漏欄位。 | 為列舉加上常用的衍生(derive)特徵,提升除錯與測試便利性。 |
最佳實踐
- 以語意命名變體:
enum HttpStatus { Ok, NotFound, InternalServerError }比enum Code { A, B, C }更具可讀性。 - 盡量讓列舉保持 「單一職責」(single responsibility):每個列舉只描述一個概念,例如
enum AuthError只處理認證錯誤。 - 使用
match而非if let:除非只關心單一變體,否則match能保證 完整性檢查。 - 善用
matches!巨集:簡化只需布林結果的判斷,如if matches!(state, State::Running)。 - 在公共 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‑like、Struct‑like 到 方法實作,本文示範了 5 個常見範例,讓你快速上手。
- 常見陷阱(如遺漏變體、過大資料、過度嵌套)與 最佳實踐(語意命名、單一職責、完整匹配)提供了實務上避免錯誤的指引。
- 在 狀態機、AST、錯誤處理、UI 事件 等真實情境中,列舉都是不可或缺的工具,能提升程式的可讀性、型別安全與未來可擴充性。
掌握列舉的定義與使用,你就能在 Rust 專案中寫出 更安全、更清晰、更具彈性的程式碼。祝你玩得開心,寫出優雅的 Rust 程式!