跳轉到

3.2 錯誤處理:Result 的實務用法#

複習:錯誤是回傳值#

1.2 節建立過心智模型:Rust 沒有例外,錯誤用 Result<T, E> 回傳。這一節講實務上怎麼用——? 運算子、自訂錯誤、以及什麼時候 panic。

? 運算子:錯誤處理的日常#

match 處理每個 Result 太囉嗦。? 運算子是慣用法:成功就取出值,失敗就立刻回傳錯誤:

use std::fs::File;
use std::io::{self, Read};

fn read_config() -> Result<String, io::Error> {
    let mut f = File::open("config.txt")?;   // 失敗?立刻 return Err
    let mut s = String::new();
    f.read_to_string(&mut s)?;               // 失敗?立刻 return Err
    Ok(s)
}

? 等價於:

let mut f = match File::open("config.txt") {
    Ok(file) => file,
    Err(e) => return Err(e),
};

? 讓錯誤沿著呼叫棧向上傳播,函式主體只寫「成功的路」,錯誤路徑一行搞定。

panic!:程式無法繼續時#

panic! 是「立即中止」:不回傳錯誤,直接崩潰。使用的判斷準則:

Result panic!
語意 可預期的失敗(檔案不存在、網路斷線) 不可復原的 bug(邏輯錯誤、契約破壞)
呼叫者能處理嗎 ✓ 必須處理 ✗ 程式中止
使用場合 絕大多數情況 請求者傳了不該發生的參數、測試斷言

慣用法:庫(library)幾乎永遠回傳 Result,把 panic 的決定權留給呼叫者;應用程式的 main 或頂層才決定怎麼處理錯誤。unwrap()/expect() 是「失敗就 panic」的快速寫法,只在原型、測試、或你確定不可能失敗時用:

let n = "42".parse::<i32>().unwrap();        // 原型階段可用
let n = "42".parse::<i32>().expect("數字解析失敗"); // 帶訊息,稍好

自訂錯誤型別#

實務專案用 enum 定義自己的錯誤(複習 2.3 節):

use std::fmt;

#[derive(Debug)]
enum AppError {
    NotFound(String),
    Parse(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::NotFound(name) => write!(f, "找不到:{}", name),
            AppError::Parse(msg) => write!(f, "解析失敗:{}", msg),
        }
    }
}

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

? 與錯誤轉換#

不同層的函式回傳不同錯誤型別時,用 From trait 讓 ? 自動轉換:

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self {
        AppError::Parse(e.to_string())
    }
}

fn read_config() -> Result<String, AppError> {
    let mut f = File::open("config.txt")?;   // io::Error 自動轉成 AppError
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

實務捷徑:手寫這些很囉嗦,社群慣用 thiserror(庫用,定義錯誤型別) 與 anyhow(應用程式用,隨手傳播) 兩個 crate(見 5.2 節)。

本章小結#

  • ? 運算子:成功取值、失敗立刻回傳——錯誤處理的日常慣用法
  • 庫回傳 Result,應用層才決定怎麼處理;unwrap 只在原型與測試用
  • 自訂錯誤用 enum + Display + Error + From(讓 ? 自動轉換)
  • 社群慣用 thiserror(庫)與 anyhow(應用)

想一想#

  1. ? 等價於什麼樣的 match?它對函式可讀性的影響是什麼?
  2. 「庫回傳 Result、應用層處理錯誤」這個分工,為什麼重要?
  3. 你專案中哪些失敗是「可預期」的(該用 Result)?哪些是 bug(該 panic)?