錯誤處理
Rust 沒有例外(exception)。它把錯誤分成兩類:無法恢復的錯誤用 panic! 中止程式,可以恢復的錯誤則用 Result<T, E> 這個回傳型別表示。因為錯誤是型別的一部分,編譯器會提醒你不能假裝它不存在。
無法恢復:panic!
panic! 會印出訊息並終止程式(預設會展開堆疊並清理資源)。適用於「程式出現了不應該發生的 bug」,例如陣列索引越界:
一般的錯誤(檔案不存在、輸入格式不對、網路失敗)不該用 panic,而要用 Result。
可恢復:Result
Result 是標準函式庫的列舉,有兩個變體:
許多標準函式庫的操作都回傳 Result。例如把字串轉成數字:
fn main() {
let inputs = ["42", "abc", "-7"];
for text in inputs {
match text.parse::<i32>() {
Ok(n) => println!("{text} → 成功:{n}"),
Err(e) => println!("{text} → 失敗:{e}"),
}
}
}
unwrap 與 expect
unwrap() 遇到 Err 或 None 會直接 panic;expect("訊息") 一樣會 panic,但能附上說明。它們適合用在範例、測試,或你確定不可能失敗的地方,正式程式碼盡量避免:
fn main() {
let n: i32 = "123".parse().expect("這是寫死的數字,一定能解析");
println!("{n}");
// 需要預設值時,用 unwrap_or 更安全
let m: i32 = "abc".parse().unwrap_or(0);
println!("{m}");
}
? 運算子:往上傳遞錯誤
函式內若某一步可能失敗,不想自己處理,就用 ? 把錯誤交給呼叫者。它的行為是:如果是 Ok,取出裡面的值繼續;如果是 Err,立刻從函式回傳這個錯誤:
use std::num::ParseIntError;
fn parse_and_double(text: &str) -> Result<i32, ParseIntError> {
let n = text.parse::<i32>()?; // 失敗時直接回傳 Err
Ok(n * 2)
}
fn main() {
println!("{:?}", parse_and_double("21"));
println!("{:?}", parse_and_double("x"));
}
? 只能用在回傳型別為 Result(或 Option)的函式中。main 也可以回傳 Result:
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let n: i32 = "100".parse()?;
println!("讀到 {n}");
Ok(())
}
Box<dyn Error> 可以裝下任何實作了 Error trait 的錯誤型別,寫小工具時很方便。
自訂錯誤型別
函式可能出現多種不同的錯誤時,可以自己定義一個 enum,並用 From 讓 ? 自動轉換:
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
Negative(i32),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Parse(e) => write!(f, "解析失敗:{e}"),
AppError::Negative(n) => write!(f, "不允許負數:{n}"),
}
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::Parse(e)
}
}
fn parse_positive(text: &str) -> Result<i32, AppError> {
let n: i32 = text.parse()?; // ParseIntError 會自動轉成 AppError
if n < 0 {
return Err(AppError::Negative(n));
}
Ok(n)
}
fn main() {
for text in ["5", "-3", "abc"] {
match parse_positive(text) {
Ok(n) => println!("{n}"),
Err(e) => println!("錯誤:{e}"),
}
}
}
Option 的常用技巧
Option 也支援 ?,並有一組好用的方法:
fn first_char_upper(s: &str) -> Option<char> {
let c = s.chars().next()?; // 空字串時直接回傳 None
Some(c.to_ascii_uppercase())
}
fn main() {
println!("{:?}", first_char_upper("rust"));
println!("{:?}", first_char_upper(""));
let name: Option<&str> = None;
println!("{}", name.unwrap_or("匿名"));
let len = Some("hello").map(|s| s.len());
println!("{:?}", len);
// 把 Option 轉成 Result
let r: Result<i32, &str> = None.ok_or("沒有值");
println!("{:?}", r);
}
該用哪一種?
| 情況 | 做法 |
|---|---|
| 程式 bug、不可能發生的狀態 | panic!、unwrap、expect |
| 可能失敗的操作 | 回傳 Result |
| 可能沒有值,但不算錯誤 | 回傳 Option |
| 想把錯誤交給呼叫者 | 用 ? |
推薦影音
錯誤處理
簡述:微軟《Beginner's Series to Rust》第 23 集,介紹 Result、Option 與錯誤處理的基本做法。
錯誤處理完整講解
簡述:Let's Get Rusty 依照官方書籍第 9 章製作,涵蓋 panic!、Result 與 ? 運算子。
下一頁:閉包與迭代器。