iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Rust

30天Rust從零到全端系列 第 12

Day 12: 列舉與模式匹配:表達多種可能性

  • 分享至 

  • xImage
  •  

前言

今天我們來學習 Rust 另一個特性 - 列舉(Enums)模式匹配(Pattern Matching)。列舉讓你能夠定義一個型別,該型別可以是幾個可能的變體之一,而模式匹配則讓你能夠安全且優雅地處理所有可能的情況。

為什麼需要列舉?

考慮以下情況,你想表示一個任務的狀態:

// 使用字串(不安全,容易出錯)
let status = "pending";  // 可能拼錯成 "pendding"

// 使用數字(不直觀)
let status = 1;  // 1 表示什麼?

// 使用常數(較好但不夠完善)
const PENDING: i32 = 0;
const IN_PROGRESS: i32 = 1;
const COMPLETED: i32 = 2;

列舉提供了更好的解決方案:

enum TaskStatus {
    Pending,
    InProgress,
    Completed,
}

let status = TaskStatus::Pending;  // 型別安全,不會拼錯

定義和使用列舉

基本列舉

#[derive(Debug)]
enum TaskStatus {
    Pending,       // 待辦
    InProgress,    // 進行中
    Completed,     // 已完成
    Cancelled,     // 已取消
}

fn main() {
    let status = TaskStatus::InProgress;
    
    println!("任務狀態: {:?}", status);
    
    // 函式可以接收列舉作為參數
    describe_status(status);
}

fn describe_status(status: TaskStatus) {
    match status {
        TaskStatus::Pending => println!("任務等待開始"),
        TaskStatus::InProgress => println!("任務正在進行"),
        TaskStatus::Completed => println!("任務已完成"),
        TaskStatus::Cancelled => println!("任務已取消"),
    }
}

帶有資料的列舉

列舉的每個變體可以攜帶不同型別和數量的資料:

#[derive(Debug)]
enum TaskPriority {
    Low,
    Medium,
    High,
    Custom(u32),           // 自訂優先順序數值
}

#[derive(Debug)]
enum TaskAssignment {
    Unassigned,
    Individual(String),     // 指派給個人
    Team(Vec<String>),      // 指派給團隊
    Department {            // 指派給部門(命名欄位)
        name: String,
        manager: String,
    },
}

fn main() {
    let priority = TaskPriority::Custom(42);
    
    let assignment = TaskAssignment::Team(vec![
        String::from("張小明"),
        String::from("李小華"),
        String::from("王小強"),
    ]);
    
    let dept_assignment = TaskAssignment::Department {
        name: String::from("開發部"),
        manager: String::from("陳經理"),
    };
    
    println!("優先順序: {:?}", priority);
    println!("指派: {:?}", assignment);
    println!("部門指派: {:?}", dept_assignment);
}

模式匹配:match 表達式

match 是 Rust 最強大的控制流運算子之一:

基本 match

fn get_priority_score(priority: TaskPriority) -> u32 {
    match priority {
        TaskPriority::Low => 1,
        TaskPriority::Medium => 5,
        TaskPriority::High => 10,
        TaskPriority::Custom(value) => value,  // 提取資料
    }
}

fn describe_assignment(assignment: TaskAssignment) -> String {
    match assignment {
        TaskAssignment::Unassigned => {
            String::from("未指派任務")
        },
        TaskAssignment::Individual(name) => {
            format!("指派給: {}", name)
        },
        TaskAssignment::Team(members) => {
            format!("指派給團隊: {} ({}人)", 
                    members.join(", "), 
                    members.len())
        },
        TaskAssignment::Department { name, manager } => {
            format!("指派給{}部門,負責人: {}", name, manager)
        },
    }
}

複雜的模式匹配

fn analyze_task_urgency(priority: TaskPriority, status: TaskStatus) -> String {
    match (priority, status) {
        // 高優先順序且待辦的任務
        (TaskPriority::High, TaskStatus::Pending) => {
            String::from("緊急!需要立即處理")
        },
        
        // 自訂優先順序超過 50 且進行中
        (TaskPriority::Custom(level), TaskStatus::InProgress) if level > 50 => {
            String::from("超高優先順序任務進行中")
        },
        
        // 任何已完成的任務
        (_, TaskStatus::Completed) => {
            String::from("任務已順利完成")
        },
        
        // 已取消的高優先順序任務
        (TaskPriority::High, TaskStatus::Cancelled) => {
            String::from("高優先順序任務被取消,需要檢視原因")
        },
        
        // 其他所有情況
        _ => String::from("任務狀態正常"),
    }
}

Option 和 Result:Rust 的重要列舉

Option:處理可能不存在的值

fn find_task_by_id(tasks: &[Task], id: u32) -> Option<&Task> {
    tasks.iter().find(|task| task.id == id)
}

fn main() {
    let tasks = vec![
        Task::new(1, String::from("學習 Rust")),
        Task::new(2, String::from("寫程式")),
    ];
    
    // 使用 match 處理 Option
    match find_task_by_id(&tasks, 1) {
        Some(task) => println!("找到任務: {}", task.title),
        None => println!("找不到任務"),
    }
    
    // 使用 if let 簡化
    if let Some(task) = find_task_by_id(&tasks, 2) {
        println!("任務標題: {}", task.title);
    }
    
    // 使用方法鏈接
    let task_title = find_task_by_id(&tasks, 1)
        .map(|task| task.title.clone())
        .unwrap_or_else(|| String::from("未知任務"));
    
    println!("任務標題: {}", task_title);
}

Result:處理可能失敗的操作

#[derive(Debug)]
enum TaskError {
    NotFound,
    AlreadyCompleted,
    InvalidStatus,
}

impl std::fmt::Display for TaskError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            TaskError::NotFound => write!(f, "找不到任務"),
            TaskError::AlreadyCompleted => write!(f, "任務已經完成"),
            TaskError::InvalidStatus => write!(f, "無效的任務狀態"),
        }
    }
}

impl std::error::Error for TaskError {}

struct TaskManager {
    tasks: Vec<Task>,
}

impl TaskManager {
    fn complete_task(&mut self, id: u32) -> Result<(), TaskError> {
        match self.tasks.iter_mut().find(|task| task.id == id) {
            Some(task) => {
                if task.is_completed {
                    Err(TaskError::AlreadyCompleted)
                } else {
                    task.is_completed = true;
                    Ok(())
                }
            },
            None => Err(TaskError::NotFound),
        }
    }
    
    fn get_task(&self, id: u32) -> Result<&Task, TaskError> {
        self.tasks.iter()
            .find(|task| task.id == id)
            .ok_or(TaskError::NotFound)
    }
}

fn main() {
    let mut manager = TaskManager {
        tasks: vec![
            Task::new(1, String::from("學習列舉")),
        ],
    };
    
    // 使用 match 處理 Result
    match manager.complete_task(1) {
        Ok(()) => println!("任務完成成功"),
        Err(e) => println!("錯誤: {}", e),
    }
    
    // 再次嘗試完成同一任務
    if let Err(e) = manager.complete_task(1) {
        println!("無法完成任務: {}", e);
    }
}

實戰:完整的任務系統

讓我們建立一個使用列舉的完整任務管理系統:

use std::fmt;

#[derive(Debug, Clone, PartialEq)]
enum Priority {
    Low,
    Medium,
    High,
    Custom(u32),
}

impl Priority {
    fn score(&self) -> u32 {
        match self {
            Priority::Low => 1,
            Priority::Medium => 5,
            Priority::High => 10,
            Priority::Custom(score) => *score,
        }
    }
}

impl fmt::Display for Priority {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Priority::Low => write!(f, "低"),
            Priority::Medium => write!(f, "中"),
            Priority::High => write!(f, "高"),
            Priority::Custom(score) => write!(f, "自訂({})", score),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
enum TaskStatus {
    Pending,
    InProgress { started_by: String },
    Completed { completed_by: String },
    Cancelled { reason: String },
}

impl fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            TaskStatus::Pending => write!(f, "待辦"),
            TaskStatus::InProgress { started_by } => write!(f, "進行中 ({})", started_by),
            TaskStatus::Completed { completed_by } => write!(f, "已完成 ({})", completed_by),
            TaskStatus::Cancelled { reason } => write!(f, "已取消 ({})", reason),
        }
    }
}

#[derive(Debug)]
enum TaskOperation {
    Create { title: String, description: String },
    Start { user: String },
    Complete { user: String },
    Cancel { reason: String },
    ChangePriority { new_priority: Priority },
}

#[derive(Debug)]
struct Task {
    id: u32,
    title: String,
    description: String,
    priority: Priority,
    status: TaskStatus,
}

impl Task {
    fn new(id: u32, title: String, description: String) -> Self {
        Task {
            id,
            title,
            description,
            priority: Priority::Medium,
            status: TaskStatus::Pending,
        }
    }
    
    fn execute_operation(&mut self, operation: TaskOperation) -> Result<String, String> {
        match operation {
            TaskOperation::Create { .. } => {
                Err(String::from("任務已經存在"))
            },
            
            TaskOperation::Start { user } => {
                match &self.status {
                    TaskStatus::Pending => {
                        self.status = TaskStatus::InProgress { started_by: user.clone() };
                        Ok(format!("任務已開始,執行者: {}", user))
                    },
                    _ => Err(String::from("只有待辦任務可以開始")),
                }
            },
            
            TaskOperation::Complete { user } => {
                match &self.status {
                    TaskStatus::InProgress { .. } => {
                        self.status = TaskStatus::Completed { completed_by: user.clone() };
                        Ok(format!("任務已完成,完成者: {}", user))
                    },
                    TaskStatus::Completed { .. } => {
                        Err(String::from("任務已經完成"))
                    },
                    _ => Err(String::from("只有進行中的任務可以完成")),
                }
            },
            
            TaskOperation::Cancel { reason } => {
                match &self.status {
                    TaskStatus::Completed { .. } => {
                        Err(String::from("已完成的任務無法取消"))
                    },
                    _ => {
                        self.status = TaskStatus::Cancelled { reason: reason.clone() };
                        Ok(format!("任務已取消,原因: {}", reason))
                    },
                }
            },
            
            TaskOperation::ChangePriority { new_priority } => {
                let old_priority = self.priority.clone();
                self.priority = new_priority.clone();
                Ok(format!("優先順序已從 {} 改為 {}", old_priority, new_priority))
            },
        }
    }
    
    fn get_urgency_level(&self) -> String {
        match (&self.priority, &self.status) {
            (Priority::High, TaskStatus::Pending) => String::from("🔴 緊急待辦"),
            (Priority::Custom(score), TaskStatus::Pending) if *score > 10 => String::from("🟠 超高優先順序"),
            (Priority::High | Priority::Custom(_), TaskStatus::InProgress { .. }) => String::from("🟡 重要進行中"),
            (_, TaskStatus::Completed { .. }) => String::from("✅ 已完成"),
            (_, TaskStatus::Cancelled { .. }) => String::from("❌ 已取消"),
            _ => String::from("⚪ 一般"),
        }
    }
}

impl fmt::Display for Task {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}] {} - {} - {}", 
               self.id, 
               self.title, 
               self.priority,
               self.status)
    }
}

struct TaskManager {
    tasks: Vec<Task>,
    next_id: u32,
}

impl TaskManager {
    fn new() -> Self {
        TaskManager {
            tasks: Vec::new(),
            next_id: 1,
        }
    }
    
    fn create_task(&mut self, title: String, description: String) -> u32 {
        let task = Task::new(self.next_id, title, description);
        let id = task.id;
        self.tasks.push(task);
        self.next_id += 1;
        id
    }
    
    fn execute_task_operation(&mut self, task_id: u32, operation: TaskOperation) -> Result<String, String> {
        match self.tasks.iter_mut().find(|task| task.id == task_id) {
            Some(task) => task.execute_operation(operation),
            None => Err(String::from("找不到指定的任務")),
        }
    }
    
    fn list_tasks_by_status(&self, filter_status: Option<TaskStatus>) {
        let filtered_tasks: Vec<_> = match filter_status {
            Some(status) => self.tasks.iter()
                .filter(|task| std::mem::discriminant(&task.status) == std::mem::discriminant(&status))
                .collect(),
            None => self.tasks.iter().collect(),
        };
        
        if filtered_tasks.is_empty() {
            println!("沒有符合條件的任務");
            return;
        }
        
        for task in filtered_tasks {
            println!("  {} {}", task, task.get_urgency_level());
        }
    }
    
    fn get_statistics(&self) -> TaskStatistics {
        let mut stats = TaskStatistics::default();
        
        for task in &self.tasks {
            match task.status {
                TaskStatus::Pending => stats.pending += 1,
                TaskStatus::InProgress { .. } => stats.in_progress += 1,
                TaskStatus::Completed { .. } => stats.completed += 1,
                TaskStatus::Cancelled { .. } => stats.cancelled += 1,
            }
            
            match task.priority {
                Priority::Low => stats.low_priority += 1,
                Priority::Medium => stats.medium_priority += 1,
                Priority::High => stats.high_priority += 1,
                Priority::Custom(_) => stats.custom_priority += 1,
            }
        }
        
        stats.total = self.tasks.len();
        stats
    }
}

#[derive(Default)]
struct TaskStatistics {
    total: usize,
    pending: usize,
    in_progress: usize,
    completed: usize,
    cancelled: usize,
    low_priority: usize,
    medium_priority: usize,
    high_priority: usize,
    custom_priority: usize,
}

impl fmt::Display for TaskStatistics {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "任務統計報告")?;
        writeln!(f, "============")?;
        writeln!(f, "總任務數: {}", self.total)?;
        writeln!(f, "狀態分佈:")?;
        writeln!(f, "  • 待辦: {}", self.pending)?;
        writeln!(f, "  • 進行中: {}", self.in_progress)?;
        writeln!(f, "  • 已完成: {}", self.completed)?;
        writeln!(f, "  • 已取消: {}", self.cancelled)?;
        writeln!(f, "優先順序分佈:")?;
        writeln!(f, "  • 低: {}", self.low_priority)?;
        writeln!(f, "  • 中: {}", self.medium_priority)?;
        writeln!(f, "  • 高: {}", self.high_priority)?;
        write!(f, "  • 自訂: {}", self.custom_priority)
    }
}

使用完整的任務系統

fn main() {
    let mut manager = TaskManager::new();
    
    // 建立任務
    let task1 = manager.create_task(
        String::from("學習 Rust 列舉"),
        String::from("深入理解列舉和模式匹配")
    );
    
    let task2 = manager.create_task(
        String::from("實作專案"),
        String::from("使用列舉建立任務管理系統")
    );
    
    let task3 = manager.create_task(
        String::from("寫測試"),
        String::from("為任務管理系統寫單元測試")
    );
    
    // 修改優先順序
    match manager.execute_task_operation(
        task1, 
        TaskOperation::ChangePriority { new_priority: Priority::High }
    ) {
        Ok(msg) => println!("✓ {}", msg),
        Err(e) => println!("✗ {}", e),
    }
    
    // 開始任務
    match manager.execute_task_operation(
        task1, 
        TaskOperation::Start { user: String::from("張小明") }
    ) {
        Ok(msg) => println!("✓ {}", msg),
        Err(e) => println!("✗ {}", e),
    }
    
    // 完成任務
    match manager.execute_task_operation(
        task1, 
        TaskOperation::Complete { user: String::from("張小明") }
    ) {
        Ok(msg) => println!("✓ {}", msg),
        Err(e) => println!("✗ {}", e),
    }
    
    // 取消任務
    match manager.execute_task_operation(
        task3, 
        TaskOperation::Cancel { reason: String::from("優先順序調整") }
    ) {
        Ok(msg) => println!("✓ {}", msg),
        Err(e) => println!("✗ {}", e),
    }
    
    println!("\n所有任務:");
    manager.list_tasks_by_status(None);
    
    println!("\n待辦任務:");
    manager.list_tasks_by_status(Some(TaskStatus::Pending));
    
    println!("\n{}", manager.get_statistics());
}

進階模式匹配技巧

守衛條件(Guard)

fn categorize_task(task: &Task) -> String {
    match (&task.priority, &task.status) {
        (Priority::Custom(score), _) if *score > 20 => String::from("超級重要"),
        (Priority::High, TaskStatus::Pending) => String::from("緊急待辦"),
        (priority, TaskStatus::InProgress { started_by }) if priority.score() > 7 => {
            format!("重要任務進行中,執行者: {}", started_by)
        },
        _ => String::from("一般任務"),
    }
}

綁定匹配

fn describe_task_detail(task: &Task) -> String {
    match &task.status {
        TaskStatus::InProgress { started_by: user } | 
        TaskStatus::Completed { completed_by: user } => {
            format!("任務相關人員: {}", user)
        },
        TaskStatus::Cancelled { reason } if reason.len() > 10 => {
            format!("詳細取消原因: {}", reason)
        },
        status => format!("狀態: {}", status),
    }
}

解構複雜結構

fn analyze_task_combination(tasks: &[Task]) -> String {
    match tasks {
        [] => String::from("沒有任務"),
        [single] => format!("只有一個任務: {}", single.title),
        [first, rest @ ..] => {
            format!("首要任務: {},其餘 {} 個任務", first.title, rest.len())
        },
    }
}

常見模式和慣用法

if let 和 while let

// if let:處理單一模式
fn process_next_task(tasks: &mut Vec<Task>) {
    if let Some(mut task) = tasks.pop() {
        println!("處理任務: {}", task.title);
        task.status = TaskStatus::InProgress { 
            started_by: String::from("系統") 
        };
    } else {
        println!("沒有待處理的任務");
    }
}

// while let:迭代處理
fn process_all_pending(tasks: &mut Vec<Task>) {
    let mut pending_tasks: Vec<_> = tasks.iter_mut()
        .filter(|task| matches!(task.status, TaskStatus::Pending))
        .collect();
    
    while let Some(task) = pending_tasks.pop() {
        task.status = TaskStatus::InProgress { 
            started_by: String::from("批次處理") 
        };
        println!("開始任務: {}", task.title);
    }
}

matches! 巨集

fn is_active_task(task: &Task) -> bool {
    matches!(task.status, TaskStatus::Pending | TaskStatus::InProgress { .. })
}

fn filter_high_priority_tasks(tasks: &[Task]) -> Vec<&Task> {
    tasks.iter()
        .filter(|task| matches!(task.priority, Priority::High | Priority::Custom(n) if n > &10))
        .collect()
}

今日練習

1. 基礎練習:交通號誌系統

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

// 實作以下功能:
// - next_light(&self) -> TrafficLight
// - can_go(&self) -> bool
// - wait_time(&self) -> u32  // 秒數
// - Display trait

2. 進階練習:計算機表達式

enum Expr {
    Number(f64),
    Add(Box<Expr>, Box<Expr>),
    Subtract(Box<Expr>, Box<Expr>),
    Multiply(Box<Expr>, Box<Expr>),
    Divide(Box<Expr>, Box<Expr>),
}

// 實作 eval(&self) -> Result<f64, String> 方法
// 例如:Expr::Add(Box::new(Expr::Number(2.0)), Box::new(Expr::Number(3.0))) 
//      應該回傳 Ok(5.0)

3. 挑戰練習:檔案系統模擬

enum FileSystemItem {
    File { 
        name: String, 
        size: u64, 
        content: String 
    },
    Directory { 
        name: String, 
        items: Vec<FileSystemItem> 
    },
}

// 實作以下功能:
// - total_size(&self) -> u64
// - find_item(&self, name: &str) -> Option<&FileSystemItem>
// - list_all_files(&self) -> Vec<String>  // 遞迴列出所有檔案名

常見錯誤和陷阱

1. 忘記處理所有情況

// 錯誤:缺少 match arm
fn bad_status_check(status: TaskStatus) -> bool {
    match status {
        TaskStatus::Completed { .. } => true,
        // 編譯錯誤:missing match arms
    }
}

// 正確:處理所有情況
fn good_status_check(status: TaskStatus) -> bool {
    match status {
        TaskStatus::Completed { .. } => true,
        _ => false,
    }
}

2. 移動語義問題

fn bad_example(task: Task) {
    match task {
        Task { status: TaskStatus::Completed { .. }, .. } => {
            // task 被部分移動
        },
        _ => {
            // println!("{}", task.title); // 錯誤!task 已被部分移動
        }
    }
}

// 正確:使用參考
fn good_example(task: &Task) {
    match &task.status {
        TaskStatus::Completed { .. } => {
            println!("任務 {} 已完成", task.title); // OK
        },
        _ => {
            println!("任務 {} 未完成", task.title); // OK
        }
    }
}

3. 過度使用 unwrap

// 危險:程式可能 panic
fn bad_find(tasks: &[Task], id: u32) -> &Task {
    tasks.iter().find(|t| t.id == id).unwrap()
}

// 安全:返回 Option
fn good_find(tasks: &[Task], id: u32) -> Option<&Task> {
    tasks.iter().find(|t| t.id == id)
}

上一篇
Day 11: 結構體:組織相關資料
下一篇
Day 13: 錯誤處理基礎
系列文
30天Rust從零到全端15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言