iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0

https://ithelp.ithome.com.tw/upload/images/20250917/20124462KA2M7PfuNm.png

Rust 逼我成為更好的工程師:錯誤處理:Result 與 Option

錯誤處理的演進:從忽略到強制

在上一篇中,我們深入了解了 Borrow Checker 如何成為最嚴格的 Code Reviewer。

現在,讓我們轉向另一個重要的主題:錯誤處理

在傳統的程式語言中,錯誤處理往往是一個容易被忽略的問題。
我們可能忘記檢查返回值,或者讓異常靜默地傳播。

Rust 的 ResultOption 類型提供了一種全新的錯誤處理方式:強制性的錯誤處理
https://ithelp.ithome.com.tw/upload/images/20250924/20124462ohr6ijRAXC.png
https://ithelp.ithome.com.tw/upload/images/20250924/20124462gGHXondBpc.png

Rust 的革命:強制性的錯誤處理

Rust 的 ResultOption 類型提供了一種全新的錯誤處理方式:

Result:成功或失敗

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 0) {
        Ok(result) => println!("Result: {}", result),
        Err(error) => println!("Error: {}", error),
    }
}

關鍵在於:你必須處理 Result 的兩種情況,編譯器不會讓你忽略錯誤。

Option:有值或無值

fn find_index(slice: &[i32], target: i32) -> Option<usize> {
    for (index, &value) in slice.iter().enumerate() {
        if value == target {
            return Some(index);
        }
    }
    None
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    match find_index(&numbers, 3) {
        Some(index) => println!("Found at index: {}", index),
        None => println!("Not found"),
    }
}

同樣地,你必須處理 Option 的兩種情況

跟其他程式語言的錯誤處理對比
https://ithelp.ithome.com.tw/upload/images/20250924/201244624p28ViyI2N.png

Result 的進階用法:鏈式操作

Rust 的 Result 類型提供了豐富的鏈式操作方法:

使用 ? 運算符

fn read_file(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

fn process_file(path: &str) -> Result<String, String> {
    let content = read_file(path)?;  // 如果出錯,直接返回
    Ok(content.to_uppercase())
}

fn main() {
    match process_file("example.txt") {
        Ok(result) => println!("{}", result),
        Err(error) => println!("Error: {}", error),
    }
}

? 運算符讓錯誤處理變得更加簡潔。

使用 mapand_then

fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()
}

fn double(n: i32) -> i32 {
    n * 2
}

fn main() {
    let result = "42"
        .parse::<i32>()
        .map(double)
        .map(|n| n.to_string());
    
    match result {
        Ok(s) => println!("Result: {}", s),
        Err(e) => println!("Error: {}", e),
    }
}

這些方法讓錯誤處理變得更加函式式。

Option 的進階用法:安全的空值處理

Rust 的 Option 類型提供了安全的空值處理:

使用 mapunwrap_or

fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some("Alice".to_string())
    } else {
        None
    }
}

fn main() {
    let user = find_user(1)
        .map(|name| format!("Hello, {}!", name))
        .unwrap_or("User not found".to_string());
    
    println!("{}", user);
}

使用 match 進行模式匹配

fn process_option(opt: Option<i32>) {
    match opt {
        Some(value) => println!("Got value: {}", value),
        None => println!("No value"),
    }
}

Rust:強制性處理

fn process_data(data: &str) -> Result<String, String> {
    if data.is_empty() {
        Err("empty data".to_string())
    } else {
        Ok(data.to_uppercase())
    }
}

fn main() {
    match process_data("") {
        Ok(result) => println!("{}", result),
        Err(error) => println!("Error: {}", error),
    }
}

錯誤處理的實際應用:API 設計

理解了 ResultOption 的哲學後,我們可以設計出更安全的 API:

錯誤的 API 設計

// 糟糕的設計:可能返回 None 但沒有明確表示
fn get_user_name(id: u32) -> String {
    if id == 1 {
        "Alice".to_string()
    } else {
        "".to_string()  // 空字串表示沒有找到
    }
}

正確的 API 設計

// 好的設計:明確表示可能失敗
fn get_user_name(id: u32) -> Option<String> {
    if id == 1 {
        Some("Alice".to_string())
    } else {
        None
    }
}

錯誤處理的進階應用:自定義錯誤類型

Rust 允許你定義自己的錯誤類型:

#[derive(Debug)]
enum MyError {
    DivisionByZero,
    InvalidInput(String),
    IoError(std::io::Error),
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            MyError::DivisionByZero => write!(f, "Division by zero"),
            MyError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
            MyError::IoError(e) => write!(f, "IO error: {}", e),
        }
    }
}

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

fn divide(a: i32, b: i32) -> Result<i32, MyError> {
    if b == 0 {
        Err(MyError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}

總結:錯誤處理的哲學轉變

Rust 的 ResultOption 類型代表了一種錯誤處理哲學的轉變:

  1. 從被動到主動:強制你處理錯誤,而不是忽略它們
  2. 從隱式到顯式:錯誤處理變得明確和可見
  3. 從執行期到編譯期:在編譯期就確保錯誤被處理
  4. 從混亂到清晰:錯誤處理變得更加結構化和可預測

拒絕「忽略錯誤」的這種嚴格性,讓我們能夠編寫出更加可靠和安全的程式碼。

在下一章中,我們將深入探討智慧指標,看看 Rust 如何用 BoxRcArc 來處理共享所有權。

相關連結與參考資源


上一篇
(Day9) Rust Borrow Checker,無情的編譯期益友
下一篇
(Day11) Rust 智慧指標(Smart pointers):從所有權到安全併發
系列文
Rust 逼我成為更好的工程師:從 Borrow Checker 看軟體設計11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言