本文 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:基本的 unwrap 與 expect
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_or 與 unwrap_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:map 與 and_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 的值。 |
最佳實踐:
- 永遠顯式處理
None:讓編譯器成為你的安全網。 - 使用語意清晰的函式:如
unwrap_or_else讓預設值的計算延遲。 - 善用
Result與Option的轉換:ok_or,ok_or_else可把Option轉成Result,方便錯誤傳遞。 - 在公共 API 中返回
Option:讓呼叫端自行決定如何處理缺失值,而不是在函式內直接 panic。
實際應用場景
1. 設定檔讀取
在讀取 JSON/YAML 設定時,某些欄位可能是可選的:
#[derive(serde::Deserialize)]
struct Config {
host: String,
port: Option<u16>, // 允許未指定,預設 8080
}
使用 unwrap_or 為 port 提供預設值:
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必須在編譯期被顯式處理。- 透過
match、if let、unwrap_or、map、and_then等工具,我們可以在不同情境下靈活且安全地取得或轉換值。 - 常見的陷阱包括盲目
unwrap、忽略None分支以及過度巢狀Option,遵守最佳實踐可讓程式碼更具可讀性與韌性。 - 在設定檔、API 回傳、資料過濾等實務場景中,
Option的正確使用是防止程式崩潰、提升使用者體驗的關鍵。
掌握了 Option 與 None 的處理方式,你就能在 Rust 中寫出 零空指標錯誤、高可靠性 的程式碼,為後續的錯誤處理與更複雜的列舉型別奠定堅實基礎。祝你在 Rust 的旅程中玩得開心、寫得安心!