[TOC]
Rust中的錯誤處理機(jī)制
在大多數(shù)現(xiàn)代語言中,都擁有一套完善的錯誤處理機(jī)制(error handing)。在一些典型的面向?qū)ο笳Z言,例如 Java 和 Python 中,錯誤使用 try…catch 語法進(jìn)行處理,但這種機(jī)制卻存在顯著的問題。
我們此處借用 Golang 的說法,Golang 認(rèn)為程序中會出現(xiàn)兩種錯誤:錯誤和異常。異常是開發(fā)者無法預(yù)料且超出自己能力范圍的錯誤,例如訪問數(shù)組越界,一旦出現(xiàn)異常,這說明程序代碼本身的邏輯就是有問題的。還有一種是錯誤,典型的比如建立 TCP 連接失敗,很顯然開發(fā)者應(yīng)該能預(yù)料到這種問題并且有能力去解決,例如在代碼中加入重試功能。錯誤是正常的程序邏輯的一部分。
像 Java 和 Python 的問題就是沒有區(qū)分這兩種錯誤,它們統(tǒng)一使用 Exception(異常) 來表示它們,這使得程序的控制流顯得特別混亂。
Rust 的錯誤處理機(jī)制與 Golang 特別相似,它將錯誤分為可恢復(fù)錯誤和不可恢復(fù)錯誤。如果遇到不可恢復(fù)錯誤程序?qū)⒈紳⑼顺觯?而可恢復(fù)錯誤則就像一個正常的函數(shù)返回值一樣。不要誤認(rèn)為程序奔潰是壞事,事實上提早崩潰(early crash)是一種被廣泛使用的設(shè)計方法論。
Rust 有兩種語法來實現(xiàn)可恢復(fù)錯誤和不可恢復(fù)錯誤,它們分別是 Result<T, E> 和 panic!。前者是一個泛型枚舉,后者則是一個宏。
不可恢復(fù)的錯誤
使用 panic! 宏是創(chuàng)建不可恢復(fù)的錯誤最簡便的用法:
fn main() {
panic!("error!");
println!("here");
}
同時還有一些常見的宏可導(dǎo)致不可恢復(fù)的錯誤
斷言:
assert!(1 == 2);
assert_eq!(1, 2); // 等效于 assert!(1 == 2)
未實現(xiàn)的代碼:
fn add(a: u32, b: u32) -> u32 {
unimplemented!()
}
fn main() {
println!("{}", add(1, 2));
}
不應(yīng)當(dāng)被訪問的代碼
程序代碼中存在一些分支,程序的開發(fā)這認(rèn)為這些分支永遠(yuǎn)不應(yīng)該被觸發(fā),如果觸發(fā)了這些分支,則很可能是上游代碼出現(xià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ù)的錯誤
Result<T, E> 是一個帶泛型的枚舉:
enum Result<T, E> {
Ok(T),
Err(E),
}
Result<T, E> 通常用于函數(shù)的返回值,用以表明該次函數(shù)調(diào)用是成功或失敗。它描述了函數(shù)調(diào)用過程可能出現(xiàn)的錯誤。Result<T, E> 可能有兩種結(jié)果之一:
-
OK(T):成功,并且獲取到 T -
Err(E):錯誤,并且獲取到錯誤描述 E
例子,使用標(biāo)準(zhǔn)庫打開一個文件:
fn main() {
match std::fs::read("/tmp/foo") {
Ok(data) => println!("{:?}", data),
Err(err) => println!("{:?}", err),
}
}
自定義錯誤與問號表達(dá)式
問號表達(dá)式
許多時候,尤其是在我們編寫庫的時候,不僅僅希望獲取錯誤,更希望錯誤可以在上下文中的進(jìn)行傳遞。有一種簡便的方式可以傳遞錯誤:使用問號表達(dá)式。當(dāng)函數(shù)的錯誤類型與當(dāng)前錯誤的類型相同時,使用 ? 可以直接將錯誤傳遞到函數(shù)外并終止函數(shù)執(zhí)行。
fn foo() -> Result<T, E> {
let x = bar()?; // bar 的錯誤類型需要與 foo 的錯誤類型相同
...
}
? 的作用是將 Result 枚舉的正常的值直接取出,如果有錯誤就將錯誤返回出去。
創(chuàng)建自定義的錯誤
#[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();
}