返回值和錯誤處理
panic 深入剖析
主動調(diào)用
fn main() {
panic!("crash and burn");
}
backtrace 棧展開
panic 時的兩種終止方式
當(dāng)出現(xiàn) panic! 時,程序提供了兩種方式來處理終止流程:棧展開和直接終止
何時該使用 panic!
先來一點背景知識,在前面章節(jié)我們粗略講過 Result<T, E> 這個枚舉類型,它是用來表示函數(shù)的返回結(jié)果:
enum Result<T, E> {
Ok(T),
Err(E),
}
當(dāng)沒有錯誤發(fā)生時,函數(shù)返回一個用 Result 類型包裹的值 Ok(T),當(dāng)錯誤時,返回一個 Err(E)。對于 Result 返回我們有很多處理方法,最簡單粗暴的就是 unwrap 和 expect,這兩個函數(shù)非常類似,我們以 unwrap 舉例:
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
返回值和?
對返回的錯誤進行處理
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
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),
},
};
}
上面代碼在匹配出 error 后,又對 error 進行了詳細(xì)的匹配解析,最終結(jié)果:
- 如果是文件不存在錯誤 ErrorKind::NotFound,就創(chuàng)建文件,這里創(chuàng)建文件File::create 也是返回 Result,因此繼續(xù)用 match 對其結(jié)果進行處理:創(chuàng)建成功,將新的文件句柄賦值給 f,如果失敗,則 panic
剩下的錯誤,一律 panic - expect 跟 unwrap 很像,也是遇到錯誤直接 panic, 但是會帶上自定義的錯誤提示信息,相當(dāng)于重載了錯誤打印的函數(shù):
失敗就 panic: unwrap 和 expect
在不需要處理錯誤的場景,例如寫原型、示例時,我們不想使用 match 去匹配 Result<T, E> 以獲取其中的 T 值,因為 match 的窮盡匹配特性,你總要去處理下 Err 分支。那么有沒有辦法簡化這個過程?有,答案就是 unwrap 和 expect。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
如果調(diào)用這段代碼時 hello.txt 文件不存在,那么 unwrap 就將直接 panic:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
expect 跟 unwrap 很像,也是遇到錯誤直接 panic, 但是會帶上自定義的錯誤提示信息,相當(dāng)于重載了錯誤打印的函數(shù):
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
報錯如下:
thread 'main' panicked at 'Failed to open hello.txt: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
傳播錯誤
程序幾乎不太可能只有 A->B 形式的函數(shù)調(diào)用,一個設(shè)計良好的程序,一個功能涉及十幾層的函數(shù)調(diào)用都有可能。而錯誤處理也往往不是哪里調(diào)用出錯,就在哪里處理,實際應(yīng)用中,大概率會把錯誤層層上傳然后交給調(diào)用鏈的上游函數(shù)進行處理,錯誤傳播將極為常見。
例如以下函數(shù)從文件中讀取用戶名,然后將結(jié)果進行返回:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
// 打開文件,f是`Result<文件句柄,io::Error>`
let f = File::open("hello.txt");
let mut f = match f {
// 打開文件成功,將file句柄賦值給f
Ok(file) => file,
// 打開文件失敗,將錯誤返回(向上傳播)
Err(e) => return Err(e),
};
// 創(chuàng)建動態(tài)字符串s
let mut s = String::new();
// 從f文件句柄讀取數(shù)據(jù)并寫入s中
match f.read_to_string(&mut s) {
// 讀取成功,返回Ok封裝的字符串
Ok(_) => Ok(s),
// 將錯誤向上傳播
Err(e) => Err(e),
}
}
有幾點值得注意:
- 該函數(shù)返回一個 Result<String, io::Error> 類型,當(dāng)讀取用戶名成功時,返回 Ok(String),失敗時,返回 Err(io:Error)
- File::open 和 f.read_to_string 返回的 Result<T, E> 中的 E 就是 io::Error
由此可見,該函數(shù)將 io::Error 的錯誤往上進行傳播,該函數(shù)的調(diào)用者最終會對 Result<String,io::Error> 進行再處理,至于怎么處理就是調(diào)用者的事,如果是錯誤,它可以選擇繼續(xù)向上傳播錯誤,也可以直接 panic,亦或?qū)⒕唧w的錯誤原因包裝后寫入 socket 中呈現(xiàn)給終端用戶。
。
傳播界的大明星: ?
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
看到?jīng)],這就是排面,相比前面的 match 處理錯誤的函數(shù),代碼直接減少了一半不止.
其實 ? 就是一個宏,它的作用跟上面的 match 幾乎一模一樣:
let mut f = match f {
// 打開文件成功,將file句柄賦值給f
Ok(file) => file,
// 打開文件失敗,將錯誤返回(向上傳播)
Err(e) => return Err(e),
};
如果結(jié)果是 Ok(T),則把 T 賦值給 f,如果結(jié)果是 Err(E),則返回該錯誤,所以 ? 特別適合用來傳播錯誤。
雖然 ? 和 match 功能一致,但是事實上 ? 會更勝一籌。
想象一下,一個設(shè)計良好的系統(tǒng)中,肯定有自定義的錯誤特征,錯誤之間很可能會存在上下級關(guān)系,例如標(biāo)準(zhǔn)庫中的 std::io::Error 和 std::error::Error,前者是 IO 相關(guān)的錯誤結(jié)構(gòu)體,后者是一個最最通用的標(biāo)準(zhǔn)錯誤特征,同時前者實現(xiàn)了后者,因此 std::io::Error 可以轉(zhuǎn)換為 std:error::Error。
明白了以上的錯誤轉(zhuǎn)換,? 的更勝一籌就很好理解了,它可以自動進行類型提升(轉(zhuǎn)換):
fn open_file() -> Result<File, Box<dyn std::error::Error>> {
let mut f = File::open("hello.txt")?;
Ok(f)
}
上面代碼中 File::open 報錯時返回的錯誤是 std::io::Error 類型,但是 open_file 函數(shù)返回的錯誤類型是 std::error::Error 的特征對象,可以看到一個錯誤類型通過 ? 返回后,變成了另一個錯誤類型,這就是 ? 的神奇之處。
根本原因是在于標(biāo)準(zhǔn)庫中定義的 From 特征,該特征有一個方法 from,用于把一個類型轉(zhuǎn)成另外一個類型,? 可以自動調(diào)用該方法,然后進行隱式類型轉(zhuǎn)換。因此只要函數(shù)返回的錯誤 ReturnError 實現(xiàn)了 From 特征,那么 ? 就會自動把 OtherError 轉(zhuǎn)換為 ReturnError。
這種轉(zhuǎn)換非常好用,意味著你可以用一個大而全的 ReturnError 來覆蓋所有錯誤類型,只需要為各種子錯誤類型實現(xiàn)這種轉(zhuǎn)換即可。
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? ? 還能實現(xiàn)鏈?zhǔn)秸{(diào)用,F(xiàn)ile::open 遇到錯誤就返回,沒有錯誤就將 Ok 中的值取出來用于下一個方法調(diào)用,簡直太精妙了.
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
// read_to_string是定義在std::io中的方法,因此需要在上面進行引用
fs::read_to_string("hello.txt")
}
從文件讀取數(shù)據(jù)到字符串中,是比較常見的操作,因此 Rust 標(biāo)準(zhǔn)庫為我們提供了 fs::read_to_string 函數(shù),該函數(shù)內(nèi)部會打開一個文件、創(chuàng)建 String、讀取文件內(nèi)容最后寫入字符串并返回,因為該函數(shù)其實與本章講的內(nèi)容關(guān)系不大,因此放在最后來講,其實只是我想震你們一下 ??
? 用于 Option 的返回
? 不僅僅可以用于 Result 的傳播,還能用于 Option 的傳播,再來回憶下 Option 的定義:
pub enum Option<T> {
Some(T),
None
}
Result 通過 ? 返回錯誤,那么 Option 就通過 ? 返回 None:
fn first(arr: &[i32]) -> Option<&i32> {
let v = arr.get(0)?;
Some(v)
}
新手用 ? 常會犯的錯誤
初學(xué)者在用 ? 時,老是會犯錯,例如寫出這樣的代碼:
fn first(arr: &[i32]) -> Option<&i32> {
arr.get(0)?
}
這段代碼無法通過編譯,切記:? 操作符需要一個變量來承載正確的值,這個函數(shù)只會返回 Some(&i32) 或者 None,只有錯誤值能直接返回,正確的值不行,所以如果數(shù)組中存在 0 號元素,那么函數(shù)第二行使用 ? 后的返回類型為 &i32 而不是 Some(&i32)。因此 ? 只能用于以下形式:
let v = xxx()?;
xxx()?.yyy()?;
帶返回值的 main 函數(shù)
在了解了 ? 的使用限制后,這段代碼你很容易看出它無法編譯:
use std::fs::File;
fn main() {
let f = File::open("hello.txt")?;
}
運行后會報錯:
$ cargo run
...
the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:48
|
3 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
4 | let greeting_file = File::open("hello.txt")?;
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
因為 ? 要求 Result<T, E> 形式的返回值,而 main 函數(shù)的返回是 (),因此無法滿足,那是不是就無解了呢?
實際上 Rust 還支持另外一種形式的 main 函數(shù):文章來源:http://www.zghlxwxcb.cn/news/detail-741301.html
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
這樣就能使用 ? 提前返回了,同時我們又一次看到了Box 特征對象,因為 std::error:Error 是 Rust 中抽象層次最高的錯誤,其它標(biāo)準(zhǔn)庫中的錯誤都實現(xiàn)了該特征,因此我們可以用該特征對象代表一切錯誤,就算 main 函數(shù)中調(diào)用任何標(biāo)準(zhǔn)庫函數(shù)發(fā)生錯誤,都可以通過 Box<dyn Error> 這個特征對象進行返回。文章來源地址http://www.zghlxwxcb.cn/news/detail-741301.html
到了這里,關(guān)于Rust錯誤處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!