柚子快報(bào)邀請(qǐng)碼778899分享:后端 Rust錯(cuò)誤處理
柚子快報(bào)邀請(qǐng)碼778899分享:后端 Rust錯(cuò)誤處理
文章目錄
返回值和錯(cuò)誤處理panic 深入剖析主動(dòng)調(diào)用backtrace 棧展開panic 時(shí)的兩種終止方式何時(shí)該使用 panic!
返回值和?對(duì)返回的錯(cuò)誤進(jìn)行處理失敗就 panic: unwrap 和 expect傳播錯(cuò)誤? 用于 Option 的返回新手用 ? 常會(huì)犯的錯(cuò)誤帶返回值的 main 函數(shù)
返回值和錯(cuò)誤處理
panic 深入剖析
主動(dòng)調(diào)用
fn main() {
panic!("crash and burn");
}
backtrace 棧展開
panic 時(shí)的兩種終止方式
當(dāng)出現(xiàn) panic! 時(shí),程序提供了兩種方式來處理終止流程:棧展開和直接終止
何時(shí)該使用 panic!
先來一點(diǎn)背景知識(shí),在前面章節(jié)我們粗略講過 Result
enum Result
Ok(T),
Err(E),
}
當(dāng)沒有錯(cuò)誤發(fā)生時(shí),函數(shù)返回一個(gè)用 Result 類型包裹的值 Ok(T),當(dāng)錯(cuò)誤時(shí),返回一個(gè) Err(E)。對(duì)于 Result 返回我們有很多處理方法,最簡(jiǎn)單粗暴的就是 unwrap 和 expect,這兩個(gè)函數(shù)非常類似,我們以 unwrap 舉例:
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
返回值和?
對(duì)返回的錯(cuò)誤進(jìn)行處理
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 后,又對(duì) error 進(jìn)行了詳細(xì)的匹配解析,最終結(jié)果:
如果是文件不存在錯(cuò)誤 ErrorKind::NotFound,就創(chuàng)建文件,這里創(chuàng)建文件File::create 也是返回 Result,因此繼續(xù)用 match 對(duì)其結(jié)果進(jìn)行處理:創(chuàng)建成功,將新的文件句柄賦值給 f,如果失敗,則 panic 剩下的錯(cuò)誤,一律 panicexpect 跟 unwrap 很像,也是遇到錯(cuò)誤直接 panic, 但是會(huì)帶上自定義的錯(cuò)誤提示信息,相當(dāng)于重載了錯(cuò)誤打印的函數(shù):
失敗就 panic: unwrap 和 expect
在不需要處理錯(cuò)誤的場(chǎng)景,例如寫原型、示例時(shí),我們不想使用 match 去匹配 Result
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
如果調(diào)用這段代碼時(shí) 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 很像,也是遇到錯(cuò)誤直接 panic, 但是會(huì)帶上自定義的錯(cuò)誤提示信息,相當(dāng)于重載了錯(cuò)誤打印的函數(shù):
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
報(bào)錯(cuò)如下:
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
傳播錯(cuò)誤
程序幾乎不太可能只有 A->B 形式的函數(shù)調(diào)用,一個(gè)設(shè)計(jì)良好的程序,一個(gè)功能涉及十幾層的函數(shù)調(diào)用都有可能。而錯(cuò)誤處理也往往不是哪里調(diào)用出錯(cuò),就在哪里處理,實(shí)際應(yīng)用中,大概率會(huì)把錯(cuò)誤層層上傳然后交給調(diào)用鏈的上游函數(shù)進(jìn)行處理,錯(cuò)誤傳播將極為常見。
例如以下函數(shù)從文件中讀取用戶名,然后將結(jié)果進(jìn)行返回:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result
// 打開文件,f是`Result<文件句柄,io::Error>`
let f = File::open("hello.txt");
let mut f = match f {
// 打開文件成功,將file句柄賦值給f
Ok(file) => file,
// 打開文件失敗,將錯(cuò)誤返回(向上傳播)
Err(e) => return Err(e),
};
// 創(chuàng)建動(dòng)態(tài)字符串s
let mut s = String::new();
// 從f文件句柄讀取數(shù)據(jù)并寫入s中
match f.read_to_string(&mut s) {
// 讀取成功,返回Ok封裝的字符串
Ok(_) => Ok(s),
// 將錯(cuò)誤向上傳播
Err(e) => Err(e),
}
}
有幾點(diǎn)值得注意:
該函數(shù)返回一個(gè) Result
。
傳播界的大明星: ?
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
看到?jīng)],這就是排面,相比前面的 match 處理錯(cuò)誤的函數(shù),代碼直接減少了一半不止.
其實(shí) ? 就是一個(gè)宏,它的作用跟上面的 match 幾乎一模一樣:
let mut f = match f {
// 打開文件成功,將file句柄賦值給f
Ok(file) => file,
// 打開文件失敗,將錯(cuò)誤返回(向上傳播)
Err(e) => return Err(e),
};
如果結(jié)果是 Ok(T),則把 T 賦值給 f,如果結(jié)果是 Err(E),則返回該錯(cuò)誤,所以 ? 特別適合用來傳播錯(cuò)誤。
雖然 ? 和 match 功能一致,但是事實(shí)上 ? 會(huì)更勝一籌。
想象一下,一個(gè)設(shè)計(jì)良好的系統(tǒng)中,肯定有自定義的錯(cuò)誤特征,錯(cuò)誤之間很可能會(huì)存在上下級(jí)關(guān)系,例如標(biāo)準(zhǔn)庫中的 std::io::Error 和 std::error::Error,前者是 IO 相關(guān)的錯(cuò)誤結(jié)構(gòu)體,后者是一個(gè)最最通用的標(biāo)準(zhǔn)錯(cuò)誤特征,同時(shí)前者實(shí)現(xiàn)了后者,因此 std::io::Error 可以轉(zhuǎn)換為 std:error::Error。
明白了以上的錯(cuò)誤轉(zhuǎn)換,? 的更勝一籌就很好理解了,它可以自動(dòng)進(jìn)行類型提升(轉(zhuǎn)換):
fn open_file() -> Result
let mut f = File::open("hello.txt")?;
Ok(f)
}
上面代碼中 File::open 報(bào)錯(cuò)時(shí)返回的錯(cuò)誤是 std::io::Error 類型,但是 open_file 函數(shù)返回的錯(cuò)誤類型是 std::error::Error 的特征對(duì)象,可以看到一個(gè)錯(cuò)誤類型通過 ? 返回后,變成了另一個(gè)錯(cuò)誤類型,這就是 ? 的神奇之處。
根本原因是在于標(biāo)準(zhǔn)庫中定義的 From 特征,該特征有一個(gè)方法 from,用于把一個(gè)類型轉(zhuǎn)成另外一個(gè)類型,? 可以自動(dòng)調(diào)用該方法,然后進(jìn)行隱式類型轉(zhuǎn)換。因此只要函數(shù)返回的錯(cuò)誤 ReturnError 實(shí)現(xiàn)了 From 特征,那么 ? 就會(huì)自動(dòng)把 OtherError 轉(zhuǎn)換為 ReturnError。
這種轉(zhuǎn)換非常好用,意味著你可以用一個(gè)大而全的 ReturnError 來覆蓋所有錯(cuò)誤類型,只需要為各種子錯(cuò)誤類型實(shí)現(xiàn)這種轉(zhuǎn)換即可。
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? ? 還能實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,F(xiàn)ile::open 遇到錯(cuò)誤就返回,沒有錯(cuò)誤就將 Ok 中的值取出來用于下一個(gè)方法調(diào)用,簡(jiǎn)直太精妙了.
use std::fs;
use std::io;
fn read_username_from_file() -> Result
// read_to_string是定義在std::io中的方法,因此需要在上面進(jìn)行引用
fs::read_to_string("hello.txt")
}
從文件讀取數(shù)據(jù)到字符串中,是比較常見的操作,因此 Rust 標(biāo)準(zhǔn)庫為我們提供了 fs::read_to_string 函數(shù),該函數(shù)內(nèi)部會(huì)打開一個(gè)文件、創(chuàng)建 String、讀取文件內(nèi)容最后寫入字符串并返回,因?yàn)樵摵瘮?shù)其實(shí)與本章講的內(nèi)容關(guān)系不大,因此放在最后來講,其實(shí)只是我想震你們一下 ?
? 用于 Option 的返回
? 不僅僅可以用于 Result 的傳播,還能用于 Option 的傳播,再來回憶下 Option 的定義:
pub enum Option
Some(T),
None
}
Result 通過 ? 返回錯(cuò)誤,那么 Option 就通過 ? 返回 None:
fn first(arr: &[i32]) -> Option<&i32> {
let v = arr.get(0)?;
Some(v)
}
新手用 ? 常會(huì)犯的錯(cuò)誤
初學(xué)者在用 ? 時(shí),老是會(huì)犯錯(cuò),例如寫出這樣的代碼:
fn first(arr: &[i32]) -> Option<&i32> {
arr.get(0)?
}
這段代碼無法通過編譯,切記:? 操作符需要一個(gè)變量來承載正確的值,這個(gè)函數(shù)只會(huì)返回 Some(&i32) 或者 None,只有錯(cuò)誤值能直接返回,正確的值不行,所以如果數(shù)組中存在 0 號(hào)元素,那么函數(shù)第二行使用 ? 后的返回類型為 &i32 而不是 Some(&i32)。因此 ? 只能用于以下形式:
let v = xxx()?;
xxx()?.yyy()?;
帶返回值的 main 函數(shù)
在了解了 ? 的使用限制后,這段代碼你很容易看出它無法編譯:
use std::fs::File;
fn main() {
let f = File::open("hello.txt")?;
}
運(yùn)行后會(huì)報(bào)錯(cuò):
$ 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
因?yàn)?? 要求 Result
實(shí)際上 Rust 還支持另外一種形式的 main 函數(shù):
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box
let f = File::open("hello.txt")?;
Ok(())
}
這樣就能使用 ? 提前返回了,同時(shí)我們又一次看到了Box 特征對(duì)象,因?yàn)?std::error:Error 是 Rust 中抽象層次最高的錯(cuò)誤,其它標(biāo)準(zhǔn)庫中的錯(cuò)誤都實(shí)現(xiàn)了該特征,因此我們可以用該特征對(duì)象代表一切錯(cuò)誤,就算 main 函數(shù)中調(diào)用任何標(biāo)準(zhǔn)庫函數(shù)發(fā)生錯(cuò)誤,都可以通過 Box
柚子快報(bào)邀請(qǐng)碼778899分享:后端 Rust錯(cuò)誤處理
參考閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。