在書籍中,中文譯者翻譯為錯誤,這是因為原文是"Error"。
但在很多語言中,都是書寫為異常。
一、概述
rust的錯誤處理與眾不同,前文已經提及:大家稱為異常,它稱為錯誤。
不可恢復的錯誤,可以大體稱為panic(恐慌)! 太率性了....
那麼我們的問題是:除了一些的確不可處理的異常,rust是否也和大部分語言那樣使用try catch來捕獲和跳過?
所謂的不可恢復錯誤,其實也可以被try catch處理?
二、用panic!宏處理錯誤
不可恢復錯誤發生的時候的通常表現:會列印出一個錯誤資訊,展開並清理棧資料,然後退出
透過一個環境變數,可以讓 Rust 在 panic 發生時列印呼叫堆疊(call stack)以便於定位 panic 的原因.
兩種退出方式
- abort- 粗暴退出,讓作業系統收拾殘局
- 回溯棧並清理它遇到的每一個函式的資料。如果需要檢視堆疊,可以把環境變數
RUST_BACKTRACE
設定為不是0即可
設定退出方式
Cargo.toml中設定,如下:
[profile.release] panic = 'abort'
上文中,panic還可以是設定為unwind
,這是預設的
上例是release,如果想在除錯的時候,那麼可以新增profile.dev
更多的配置,可以參考 https://www.rustwiki.org.cn/zh-CN/cargo/index.html
觸發方式
- 直接呼叫panic!宏
- 一些可能導致不可恢復的錯誤,例如除以0,越絕訪問向量
示例一、直接觸發
輸出一大堆!!!
示例二、越界訪問
輸出太多,擷取了比較又意義的部分:已經足夠明白那裡發生錯誤的。
毫無疑問,對於第二種情況,在其它語言種,一般簡單try catch就可以處理了!
三、用Result處理可恢復錯誤
Resutt<T,E>是什麼
一個列舉型別,類似Option。
有兩個成員:Ok,Err
和Option的Some,None一樣,Ok,Err也是可以直接使用,不需要書寫字首,因為它們都在prelude中匯入了。
注意:prelude是序章的意思,可以理解為rust為每一個應用自動匯入的部分。
處理Result,處理錯誤型別
由於Result是Option,所以,可以這樣使用:
use std::fs::File; use std::io::ErrorKind; fn main() { let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {e:?}"), }, other_error => { panic!("Problem opening the file: {other_error:?}"); } }, }; }
或者
use std::fs::File; use std::io::ErrorKind; fn main() { let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
失敗的兩種處理:unwrap,expect
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); let greeting_file2 = File::open("hello.txt") .expect("hello.txt should be included in this project"); }
unwrap和expect的區別在於:後者會在觸發錯誤的時候,直接列印引數值。
expect方法內部大體是這樣的:
#[inline] #[track_caller] #[stable(feature = "result_expect", since = "1.4.0")] pub fn expect(self, msg: &str) -> T where E: fmt::Debug, { match self { Ok(t) => t, Err(e) => unwrap_failed(msg, &e), } }
邏輯:正常就返回值、不正常的則返回一個異常(不可恢復的錯誤,退出)。
3.1傳播錯誤-如何處理錯誤
在大部分的語言中,有兩種方式:繼續丟擲或者捕獲之後做其它處理,例如java就是這樣處理的。
這個內容比較多,所以單獨一個小章節說明:
- 常規處理-根據result結果返回需要的內容
- 使用?簡化處理
- 不是任何地方都可以用?
- 讓main()函式可以支援?
- Box<dyn Error>
常規處理-這個沒有什麼好說的。
使用?簡化處理
1.正常程式碼
use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let username_file_result = File::open("hello.txt"); let mut username_file = match username_file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(e) => Err(e), } }
2.簡化程式碼-1
use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) }
?這裡表示 match xxxx 一段內容。如果出現異常,已經退出了。
3. 繼續簡化的程式碼_2
use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username = String::new(); File::open("hello.txt")?.read_to_string(&mut username)?; Ok(username) }
這裡主要使用鏈式寫法簡化了。毫無疑問,這個鏈式的還是比較受到歡迎的!!!
哪裡不能用?
函式的返回值不同於?
最典型例子:
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt")?; }
這是因為main的返回型別是() -- rust的什麼都奇怪的很,雖然最核心大家都差不多。
讓main支援?
有什麼驚人之舉嗎?沒有,就是修改返回型別。
use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let greeting_file = File::open("hello.txt")?; Ok(()) }
四、要不要panic!宏
原文說了一堆口水話,對於有經驗的工程師而言,沒有什麼價值。略!
五、小結
- rust的錯誤處理和其它部分一樣,力圖做到與眾不同- Error,panic!、Ok、Err、?、Box 這是憑空多出來的一些新概念(老東西換新的名稱)
- 暫時沒有介紹如何讓所謂的不可恢復錯誤程式設計可以忽略的異常 --類似越界訪問,在其它語言再正常不過了
- 利用Resut和?某種程度上,會讓程式碼看起來比一些語言好看一些(僅僅是好看而已)
- 注意異常處理原則。 或者注意團隊中的統一處理原則
- 一個最重要的內容本章並沒有提到:發生致命錯誤的時候,如何避免退出。這種要求十分常見,例如越界訪問。但這個應該是一定有的,只是本章並沒有提到。