# 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`