枚舉(Enum)#
枚舉(Enum) 讓你定義一個型別,它的值只能是幾個固定的 變體(Variant) 之一。
Rust 的枚舉比許多語言更強大,因為每個變體可以攜帶不同型別的資料。
1. 定義枚舉#
1
2
3
4
5
6
7
8
9
10
| enum Direction {
North,
South,
East,
West,
}
fn main() {
let dir = Direction::North;
}
|
2. 攜帶資料的枚舉#
每個變體可以攜帶不同型別和數量的資料:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| enum Shape {
Circle(f64), // 半徑
Rectangle(f64, f64), // 寬、高
Triangle(f64, f64, f64), // 三邊
}
fn area(shape: Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
|
3. 枚舉的方法#
和結構體一樣,枚舉也可以使用 impl 定義方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
impl Coin {
fn value(&self) -> u32 {
match self {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
fn main() {
let c = Coin::Quarter;
println!("面值:{} 分", c.value());
}
|
4. Option<T>#
許多語言用 null 表示「沒有值」,但 null 很容易在程式執行時突然炸掉(NullPointerException)。
Rust 完全沒有 null,改用 Option<T> 枚舉明確表達「值 可能存在、也可能不存在 」:
1
2
3
4
| enum Option<T> {
Some(T), // 有值,攜帶型別 T 的資料
None, // 沒有值
}
|
T 是泛型型別參數,代表實際存放的資料型別。例如 Option<i32> 表示可能有一個 i32,Option<String> 表示可能有一個 String。泛型的完整說明見 CH14 泛型。
建立 Option#
1
2
3
4
5
6
| let a: Option<i32> = Some(42); // 有值
let b: Option<i32> = None; // 沒有值
// 型別通常可以推斷,不需要手動標注
let name: Option<&str> = Some("Alice");
let empty: Option<&str> = None;
|
為什麼要用 Option#
Rust 的型別系統把「可能沒有值」這件事 寫進型別裡 。
如果一個函數可能找不到結果,就回傳 Option<T>;如果確定一定有值,就直接回傳 T。
這樣編譯器就能強制你在使用值之前,先處理「沒有值」的情況,而不是等到執行時才爆炸。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None // 找不到時明確回傳 None
}
}
fn main() {
// 編譯器要求你處理 None,不能直接當 String 使用
match find_user(1) {
Some(name) => println!("找到用戶:{name}"),
None => println!("用戶不存在"),
}
}
|
常用方法#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| fn main() {
let some_val: Option<i32> = Some(42);
let no_val: Option<i32> = None;
// unwrap_or:有值取值,沒值用預設值
println!("{}", some_val.unwrap_or(0)); // 42
println!("{}", no_val.unwrap_or(0)); // 0
// unwrap_or_else:沒值時執行閉包產生預設值
println!("{}", no_val.unwrap_or_else(|| 1 + 1)); // 2
// is_some / is_none:檢查狀態
println!("{}", some_val.is_some()); // true
println!("{}", no_val.is_none()); // true
// map:對 Some 內的值做轉換,None 原樣傳遞
let doubled = some_val.map(|v| v * 2);
println!("{:?}", doubled); // Some(84)
println!("{:?}", no_val.map(|v| v * 2)); // None
// and_then:串接可能失敗的步驟(類似 ? 運算子)
let result = some_val
.and_then(|v| if v > 10 { Some(v) } else { None })
.and_then(|v| Some(v.to_string()));
println!("{:?}", result); // Some("42")
// unwrap:取出 Some 的值,若是 None 則 panic(只在確定不會是 None 時使用)
println!("{}", some_val.unwrap()); // 42
}
|
if let 簡化寫法#
只在乎有值的情況時,用 if let 比 match 簡潔:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
let name = find_user(1);
// match 寫法
match name {
Some(n) => println!("你好,{n}"),
None => {},
}
// if let 寫法(等效,更簡潔)
if let Some(n) = find_user(1) {
println!("你好,{n}");
}
}
|
6. Result<T, E>#
Result<T, E> 是 Rust 處理錯誤的核心機制,用來表達 可能失敗的操作 :
1
2
3
4
| enum Result<T, E> {
Ok(T), // 成功,攜帶型別 T 的結果值
Err(E), // 失敗,攜帶型別 E 的錯誤值
}
|
T 和 E 是泛型型別參數,由使用情境決定。例如讀取檔案時,T 是 String(成功取得內容),E 是 io::Error(失敗的原因)。
建立 Result#
1
2
3
4
5
6
7
| fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除數不能為零")) // 回傳錯誤
} else {
Ok(a / b) // 回傳成功值
}
}
|
用 match 處理 Result#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除數不能為零"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("結果:{result}"), // 結果:5
Err(msg) => println!("錯誤:{msg}"),
}
match divide(10.0, 0.0) {
Ok(result) => println!("結果:{result}"),
Err(msg) => println!("錯誤:{msg}"), // 錯誤:除數不能為零
}
}
|
常用方法#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fn main() {
let ok: Result<i32, &str> = Ok(42);
let err: Result<i32, &str> = Err("出錯了");
// unwrap_or:成功取值,失敗用預設值
println!("{}", ok.unwrap_or(0)); // 42
println!("{}", err.unwrap_or(0)); // 0
// is_ok / is_err:檢查狀態
println!("{}", ok.is_ok()); // true
println!("{}", err.is_err()); // true
// map:對 Ok 內的值做轉換,Err 原樣傳遞
let doubled = ok.map(|v| v * 2);
println!("{:?}", doubled); // Ok(84)
// unwrap:取出 Ok 值,若是 Err 則 panic(只在確定不會失敗時使用)
println!("{}", ok.unwrap()); // 42
}
|
Result 與 Option 的差別#
| Option<T> | Result<T, E> |
|---|
| 失敗變體 | None(無額外資訊) | Err(E)(攜帶錯誤原因) |
| 使用場景 | 值可能不存在 | 操作可能失敗,且需要知道原因 |
| 典型例子 | 查詢 HashMap | 讀取檔案、解析字串 |
7. ? 運算子#
? 是專門用來傳遞錯誤的語法糖,只能用在回傳 Result 或 Option 的函數內。
用於 Result#
在 Result 值後面加 ?,效果是:
- 若是
Ok(v):取出 v,繼續執行後面的程式碼 - 若是
Err(e): 立即結束函數 ,把 Err(e) 作為函數的回傳值
1
2
3
4
5
6
7
8
| use std::fs;
use std::io;
fn read_file(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?; // 若失敗,整個函數在此結束並回傳 Err
let trimmed = content.trim().to_string(); // 只有成功時才會執行到這行
Ok(trimmed)
}
|
等同於手動寫:
1
2
3
4
5
6
7
| fn read_file(path: &str) -> Result<String, io::Error> {
let content = match fs::read_to_string(path) {
Ok(v) => v, // 成功:取出值繼續
Err(e) => return Err(e), // 失敗:提前結束函數
};
Ok(content.trim().to_string())
}
|
串接多個可能失敗的步驟#
沒有 ? 時,每一步都要 match,程式碼層層嵌套:
1
2
3
4
5
6
7
8
9
10
11
| fn process(path: &str) -> Result<usize, io::Error> {
match fs::read_to_string(path) {
Err(e) => Err(e),
Ok(content) => {
match content.trim().parse::<usize>() {
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
Ok(n) => Ok(n * 2),
}
}
}
}
|
用 ? 後,每一步失敗就自動往上傳遞,程式碼變得線性易讀:
1
2
3
4
5
6
7
8
9
| use std::fs;
use std::io;
fn process(path: &str) -> Result<usize, io::Error> {
let content = fs::read_to_string(path)?; // 步驟 1
let n: usize = content.trim().parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // 步驟 2
Ok(n * 2) // 步驟 3
}
|
用於 Option#
? 也可以用在回傳 Option 的函數:
- 若是
Some(v):取出 v 繼續執行 - 若是
None: 立即結束函數 ,回傳 None
1
2
3
4
5
6
7
8
9
10
| fn first_word(s: &str) -> Option<&str> {
let words: Vec<&str> = s.split_whitespace().collect();
let first = words.first()?; // 若 words 是空的(None),整個函數回傳 None
Some(first)
}
fn main() {
println!("{:?}", first_word("hello world")); // Some("hello")
println!("{:?}", first_word("")); // None
}
|
使用限制#
? 只能在函數回傳型別與 ? 作用的型別相符時使用:
1
2
3
4
| fn main() {
// let n: i32 = "42".parse()?; // 錯誤!main 回傳 (),不是 Result
println!("? 只能用在回傳 Result 或 Option 的函數內");
}
|
若要在 main 中使用 ?,需要改變其回傳型別:
1
2
3
4
5
| fn main() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?; // parse 失敗時自動回傳 Err,現在可以了
println!("{n}");
Ok(())
}
|
Reference#
https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html