---
# System prepended metadata

title: Error Handling

---

# Error Handling

> 異常在多數語言中會統一以 `Exception` 來處理, 而 `Java` 提供了 `Exception` 和 `Error` 來進一步區分是遇到比較輕微的「例外」或比較嚴重的「錯誤」, 並把 `Exception` 區分為執行時期 (i.e. 使用者自己不當使用) 或非執行時期 (內部錯誤) 的例外。
>
> Rust 也採用將異常依程度分成兩種, 並更進一步的規定處理這兩種例外的方法。

這兩種異常的使用有一些指導性原則, 當遇到...

1. 測試
2. 程式原型的標記
3. 一定不會錯
4. 別人調用你的函式
5. 自己設定的不變量或規則被破壞

此時使用 `panic!` 或會調用 `panic!` 的 `unwrap` 或 `expect`。

其他情況則建議用 `Result`。

## 例外分類

### `Panic`: Unrecoverable Errors

若遇到嚴重而不可回復的錯誤, 若我們自己撰寫時偵測到的話, 可以調用 `panic!("[opt]Error Msg")` 來主動引發 panic, 或者調用其它庫時使用不慎, 其內部引發 panic。

`panic!()` 會...

1. 印出錯誤訊息
2. `unwind` (展開) `stack` (呼叫函式的 stack)
3. 退出程式

這裡的 `unwind stack` Rust 會一步步回朔並清理每個區塊的記憶體, 工作量大, 可以改成 `abort`, 清理資料的工作便會由作業系統來處理, 這樣的一個明顯好處是, 如此產生的執行檔會更輕巧。

而改成 `abort` 的方法是在 `Cargo.toml` 中添加

```txt
[profile.release]
panic = 'abort'
```

### `Result`: Recoverable Errors

`Result` 是一個 `enum`, 會自動預先導入 (prelude), 同 `Option`。

```Rust=
enum Result<T, E> {
    Ok(T),
    Err(E)
}
```

來個處理回傳值為 `Result<T, E>` 的例子。

```Rust=
/* use std::fs::File; */
/* use std::io::ErrorKind; */

let file_name = "Eroiko.txt";

/* 土法煉鋼法 */
let old_fashion_f = File::open(file_name);
let old_fashion_f = match old_fashion_f {
    Ok(file) => file,
    Err(e) => match e.kind() {
        ErrorKind::NotFound => match File::create(file_name) {
            Ok(_file) => _file,
            Err(e) => panic!("Error occur when creating file: {}", e),
        }
        otherwise => panic!("Error occur when opening file: {:?}", otherwise),
    }
};

/* 用 Closure 來處理 */
let nice_f = File::open(file_name).unwrap_or_else( |e| {
    if e.kind() == ErrorKind::NotFound {
        File::create(file_name).unwrap_or_else(|e| {
            panic!("Error occur when creating file: {}", e);
        })
    } else {
        panic!("Error occur when opening file: {:?}", e);
    }
});

/* 懶得處理例外 */
// + 1. 直接解開 Result<T, E>, 遇到錯誤時無自定義錯誤訊息
let unwrap_f = File::open(file_name).unwrap();
// * 以上等價於
let real_unwrap_f = File::open(file_name);
let real_unwrap_f = match real_unwrap_f {
    Ok(file) => file,
    Err(e) => {
        panic!("Error occur when opening file: {:?}", e)
    }
};

// + 2. 解開 Result 時遇到錯誤，會印出自定義錯誤訊息的版本
let lazy_f = File::open(file_name).expect("Error occur when opening file");
```

## Error Propagating

> 錯誤傳遞

透過將錯誤包裝於 `Err` 後 `return` 來實現，這種函式會返回一個 `Result<T, E>`。

舉例如下。

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

fn file_read(file_name: &str) -> Result<String, io::Error> {
    let f = File::open(file_name);

    let mut f = match f { // f is mutable since f.read_to_string is mutative
        Ok(fl) => fl,
        Err(e) => return Err(e),
    };

    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
```

不過上面其實很冗餘，可以用 `?` 運算子來簡化處理 `match` 的調用結果。

```Rust=
fn file_read_with_qm(file_name: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open(file_name)?.read_to_string(&mut s)?;
    Ok(s)
}
```

`?` 的作用是，若對一個為 `Result::Ok` 的表達式, 會將其拆包; 若為 `Result::Err`, 則會<font color = mediumSeaGreen>將此 `Err` 隱式的<font color=#cd4c4c size=4>以 `trait std::convert::From` 中定義的 `from` 函式轉換成對應返回之 `Err` 型別 </font> 後返回</font>, 這等價於上面的寫法。

所以這意味著 `Err<A>` 必須實作 `trait std::convert::From<A, B>`, 使原 `Err<A>` 可以轉換成的 `Err<B>`。

另外, 理所當然的, 只有當一個函式的返回值為 `Result` 時, 才可以在其 scope 內使用 `?` 運算子。

> 另外提一下, `Box<dyn Error>` 表示所有 `Error`