[TOC]
Rust中的錯(cuò)誤處理機(jī)制
在大多數(shù)現(xiàn)代語(yǔ)言中从诲,都擁有一套完善的錯(cuò)誤處理機(jī)制(error handing)。在一些典型的面向?qū)ο笳Z(yǔ)言,例如 Java 和 Python 中,錯(cuò)誤使用 try…catch 語(yǔ)法進(jìn)行處理,但這種機(jī)制卻存在顯著的問(wèn)題窒悔。
我們此處借用 Golang 的說(shuō)法,Golang 認(rèn)為程序中會(huì)出現(xiàn)兩種錯(cuò)誤:錯(cuò)誤和異常敌买。異常是開(kāi)發(fā)者無(wú)法預(yù)料且超出自己能力范圍的錯(cuò)誤简珠,例如訪問(wèn)數(shù)組越界,一旦出現(xiàn)異常虹钮,這說(shuō)明程序代碼本身的邏輯就是有問(wèn)題的北救。還有一種是錯(cuò)誤荐操,典型的比如建立 TCP 連接失敗,很顯然開(kāi)發(fā)者應(yīng)該能預(yù)料到這種問(wèn)題并且有能力去解決珍策,例如在代碼中加入重試功能托启。錯(cuò)誤是正常的程序邏輯的一部分。
像 Java 和 Python 的問(wèn)題就是沒(méi)有區(qū)分這兩種錯(cuò)誤攘宙,它們統(tǒng)一使用 Exception(異常) 來(lái)表示它們屯耸,這使得程序的控制流顯得特別混亂。
Rust 的錯(cuò)誤處理機(jī)制與 Golang 特別相似蹭劈,它將錯(cuò)誤分為可恢復(fù)錯(cuò)誤和不可恢復(fù)錯(cuò)誤疗绣。如果遇到不可恢復(fù)錯(cuò)誤程序?qū)⒈紳⑼顺觯?而可恢復(fù)錯(cuò)誤則就像一個(gè)正常的函數(shù)返回值一樣。不要誤認(rèn)為程序奔潰是壞事铺韧,事實(shí)上提早崩潰(early crash)是一種被廣泛使用的設(shè)計(jì)方法論多矮。
Rust 有兩種語(yǔ)法來(lái)實(shí)現(xiàn)可恢復(fù)錯(cuò)誤和不可恢復(fù)錯(cuò)誤,它們分別是 Result<T, E>
和 panic!
哈打。前者是一個(gè)泛型枚舉塔逃,后者則是一個(gè)宏。
不可恢復(fù)的錯(cuò)誤
使用 panic!
宏是創(chuàng)建不可恢復(fù)的錯(cuò)誤最簡(jiǎn)便的用法:
fn main() {
panic!("error!");
println!("here");
}
同時(shí)還有一些常見(jiàn)的宏可導(dǎo)致不可恢復(fù)的錯(cuò)誤
斷言:
assert!(1 == 2);
assert_eq!(1, 2); // 等效于 assert!(1 == 2)
未實(shí)現(xiàn)的代碼:
fn add(a: u32, b: u32) -> u32 {
unimplemented!()
}
fn main() {
println!("{}", add(1, 2));
}
不應(yīng)當(dāng)被訪問(wèn)的代碼
程序代碼中存在一些分支料仗,程序的開(kāi)發(fā)這認(rèn)為這些分支永遠(yuǎn)不應(yīng)該被觸發(fā)湾盗,如果觸發(fā)了這些分支,則很可能是上游代碼出現(xiàn)了問(wèn)題:
fn divide_by_three(x: u32) -> u32 { // one of the poorest implementations of x/3
for i in 0.. {
if 3*i < i { panic!("u32 overflow"); }
if x < 3*i { return i-1; }
}
unreachable!();
}
可恢復(fù)的錯(cuò)誤
Result<T, E>
是一個(gè)帶泛型的枚舉:
enum Result<T, E> {
Ok(T),
Err(E),
}
Result<T, E>
通常用于函數(shù)的返回值立轧,用以表明該次函數(shù)調(diào)用是成功或失敗格粪。它描述了函數(shù)調(diào)用過(guò)程可能出現(xiàn)的錯(cuò)誤。Result<T, E>
可能有兩種結(jié)果之一:
-
OK(T)
:成功氛改,并且獲取到 T -
Err(E)
:錯(cuò)誤帐萎,并且獲取到錯(cuò)誤描述 E
例子,使用標(biāo)準(zhǔn)庫(kù)打開(kāi)一個(gè)文件:
fn main() {
match std::fs::read("/tmp/foo") {
Ok(data) => println!("{:?}", data),
Err(err) => println!("{:?}", err),
}
}
自定義錯(cuò)誤與問(wèn)號(hào)表達(dá)式
問(wèn)號(hào)表達(dá)式
許多時(shí)候胜卤,尤其是在我們編寫(xiě)庫(kù)的時(shí)候疆导,不僅僅希望獲取錯(cuò)誤,更希望錯(cuò)誤可以在上下文中的進(jìn)行傳遞瑰艘。有一種簡(jiǎn)便的方式可以傳遞錯(cuò)誤:使用問(wèn)號(hào)表達(dá)式是鬼。當(dāng)函數(shù)的錯(cuò)誤類型與當(dāng)前錯(cuò)誤的類型相同時(shí)肤舞,使用 ?
可以直接將錯(cuò)誤傳遞到函數(shù)外并終止函數(shù)執(zhí)行紫新。
fn foo() -> Result<T, E> {
let x = bar()?; // bar 的錯(cuò)誤類型需要與 foo 的錯(cuò)誤類型相同
...
}
?
的作用是將 Result 枚舉的正常的值直接取出,如果有錯(cuò)誤就將錯(cuò)誤返回出去李剖。
創(chuàng)建自定義的錯(cuò)誤
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
pub enum Error {
IO(std::io::ErrorKind),
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::IO(error.kind())
}
}
fn do_read_file() -> Result<(), Error>{
let data = std::fs::read("/tmp/foo")?;
let data_str = std::str::from_utf8(&data).unwrap();
println!("{}", data_str);
Ok(())
}
fn main() {
do_read_file().unwrap();
}