Rust中的錯誤處理機(jī)制

[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();
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容