本文 AI 產出,尚未審核

Rust 課程 – 列舉與模式匹配

主題:Option 列舉(處理 None


簡介

在 Rust 中,安全地處理可能不存在的值是語言設計的核心之一。
Option<T> 這個列舉(enum)正是為了取代傳統的 null,提供編譯期保證,避免因為未檢查的空指標而產生的執行時錯誤。

對於剛踏入 Rust 的學習者而言,掌握 Option 的使用方式與 模式匹配(pattern matching)是日後撰寫可靠程式的基礎。
本篇文章將從概念說明、實作範例、常見陷阱到實務應用,完整介紹如何在 None 情況下安全地處理資料。


核心概念

1. Option 的定義

enum Option<T> {
    Some(T),   // 包含一個有效值
    None,      // 沒有值
}
  • Option<T>泛型,可以包裝任意型別 T
  • None 並非「空指標」或「未初始化」的代名詞,而是 明確的、可辨識的變種,必須在編譯期被處理。

重點:若程式碼未處理 None,編譯器會發出警告或錯誤,迫使開發者正視每一個可能的空值情形。

2. 常見的取得值方式

方法 說明
unwrap() 直接取得 Some 內的值,若是 None 會 panic。
expect(msg) unwrap(),但可自訂 panic 訊息。
unwrap_or(default) 若為 None,回傳提供的 default
`unwrap_or_else(
`map( v
`and_then( v

3. 模式匹配(match)處理 Option

match 是 Rust 最強大的控制流之一,能夠 完整列舉所有可能的變種,確保 None 不會被遺漏。

fn describe(opt: Option<i32>) -> String {
    match opt {
        Some(v) => format!("值為 {}", v),
        None    => "沒有值".to_string(),
    }
}

4. if let 簡化寫法

當只關心 Some 的情況時,if let 能讓程式更簡潔:

if let Some(v) = maybe_number {
    println!("取得數字:{}", v);
} else {
    println!("沒有數字");
}

程式碼範例

以下提供 5 個實用範例,說明在不同情境下如何安全處理 None

範例 1:基本的 unwrapexpect

fn main() {
    let some_val = Some(42);
    let none_val: Option<i32> = None;

    // 正常取得
    println!("some_val = {}", some_val.unwrap());

    // 使用 expect,提供自訂錯誤訊息
    // 若執行到此行會 panic,訊息為 "預期有值,但得到 None"
    // println!("none_val = {}", none_val.expect("預期有值,但得到 None"));
}

提醒:除非真的確定不會是 None,否則盡量避免直接 unwrap,以免在正式環境中產生 panic。


範例 2:unwrap_orunwrap_or_else

fn main() {
    let config: Option<&str> = None;

    // 提供固定的預設值
    let host = config.unwrap_or("localhost");
    println!("使用的主機:{}", host);   // 輸出 localhost

    // 使用閉包延遲計算預設值(適合成本較高的運算)
    let timeout = config.unwrap_or_else(|| {
        // 假設此函式需要讀取檔案或執行較重的計算
        compute_default_timeout()
    });
    println!("逾時設定:{} 秒", timeout);
}

fn compute_default_timeout() -> u32 {
    // 模擬昂貴的運算
    30
}

範例 3:mapand_then 進行鏈式轉換

fn main() {
    let raw: Option<&str> = Some("  Rust  ");

    // 只在 Some 時執行 trim,None 直接傳遞
    let trimmed = raw.map(|s| s.trim());
    println!("trimmed: {:?}", trimmed); // Some("Rust")

    // 進一步轉成大寫,使用 and_then 讓結果仍是 Option
    let upper = trimmed.and_then(|s| Some(s.to_uppercase()));
    println!("upper: {:?}", upper); // Some("RUST")
}

範例 4:使用 match 進行錯誤處理

fn divide(a: i32, b: Option<i32>) -> Result<i32, String> {
    match b {
        Some(0) => Err("除數不能為 0".to_string()),
        Some(v) => Ok(a / v),
        None    => Err("缺少除數".to_string()),
    }
}

fn main() {
    let result = divide(10, Some(2));
    println!("{:?}", result); // Ok(5)

    let err = divide(10, None);
    println!("{:?}", err); // Err("缺少除數")
}

範例 5:if let 結合 else 處理 None

fn greet(name: Option<&str>) {
    if let Some(n) = name {
        println!("哈囉,{}!", n);
    } else {
        println!("哈囉,陌生人!");
    }
}

fn main() {
    greet(Some("小明"));
    greet(None);
}

常見陷阱與最佳實踐

陷阱 說明 建議的做法
直接 unwrap None 時會 panic,導致程式崩潰。 使用 unwrap_or, expect(加上明確訊息),或透過 match/if let 明確處理。
忽略 None 分支 match 必須覆蓋所有變種,若使用 _ 隱藏,可能掩蓋錯誤。 盡量寫出完整的 Some / None 分支,或在 _ 中加入 unreachable!() 以提示不應到達的情況。
過度使用 Option<Option<T>> 多層 Option 會讓程式碼難以閱讀。 考慮使用 flatten() 或重新設計資料結構,避免不必要的巢狀。
忘記 Copy/Clone 的所有權轉移 match 中直接使用 Some(v) 會把所有權移走。 若仍需保留原值,可使用 Some(ref v)Some(v.clone())(前提是 T: Clone)。
在迭代器中忘記 filter_map 手動 match 會產生冗長程式。 使用 Iterator::filter_map 直接過濾 None 並取得 Some 的值。

最佳實踐

  1. 永遠顯式處理 None:讓編譯器成為你的安全網。
  2. 使用語意清晰的函式:如 unwrap_or_else 讓預設值的計算延遲。
  3. 善用 ResultOption 的轉換ok_or, ok_or_else 可把 Option 轉成 Result,方便錯誤傳遞。
  4. 在公共 API 中返回 Option:讓呼叫端自行決定如何處理缺失值,而不是在函式內直接 panic。

實際應用場景

1. 設定檔讀取

在讀取 JSON/YAML 設定時,某些欄位可能是可選的:

#[derive(serde::Deserialize)]
struct Config {
    host: String,
    port: Option<u16>,   // 允許未指定,預設 8080
}

使用 unwrap_orport 提供預設值:

let cfg: Config = serde_json::from_str(json_str)?;
let port = cfg.port.unwrap_or(8080);

2. 網路回傳的可選欄位

REST API 常回傳部分欄位可能為 null,對應到 Rust 時會是 Option<T>

#[derive(serde::Deserialize)]
struct User {
    id: u64,
    nickname: Option<String>, // 使用者可能未設定暱稱
}

在前端顯示時:

match user.nickname {
    Some(ref name) => println!("暱稱:{}", name),
    None => println!("尚未設定暱稱"),
}

3. 迭代器與資料過濾

從資料庫取出多筆紀錄,某些欄位可能缺失:

let ids: Vec<Option<u32>> = vec![Some(1), None, Some(3)];
let valid_ids: Vec<u32> = ids.into_iter().filter_map(|id| id).collect();
// valid_ids = [1, 3]

利用 filter_map 直接過濾 None,寫出簡潔且安全的程式。


總結

  • Option<T> 是 Rust 用以取代 null安全列舉None 必須在編譯期被顯式處理。
  • 透過 matchif letunwrap_ormapand_then 等工具,我們可以在不同情境下靈活且安全地取得或轉換值。
  • 常見的陷阱包括盲目 unwrap、忽略 None 分支以及過度巢狀 Option,遵守最佳實踐可讓程式碼更具可讀性與韌性。
  • 在設定檔、API 回傳、資料過濾等實務場景中,Option 的正確使用是防止程式崩潰、提升使用者體驗的關鍵。

掌握了 OptionNone 的處理方式,你就能在 Rust 中寫出 零空指標錯誤高可靠性 的程式碼,為後續的錯誤處理與更複雜的列舉型別奠定堅實基礎。祝你在 Rust 的旅程中玩得開心、寫得安心!