溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Rust錯(cuò)誤處理有哪些

發(fā)布時(shí)間:2021-10-12 15:46:20 來(lái)源:億速云 閱讀:204 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“Rust錯(cuò)誤處理有哪些”,在日常操作中,相信很多人在Rust錯(cuò)誤處理有哪些問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Rust錯(cuò)誤處理有哪些”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

Rust錯(cuò)誤處理有哪些

錯(cuò)誤處理是編程語(yǔ)言中很重要的一個(gè)方面。目前,錯(cuò)誤處理的方式分為兩類,第一類是以C語(yǔ)言為首的基于返回值的錯(cuò)誤處理方案,第二類是以Java語(yǔ)言為首的基于異常的錯(cuò)誤處理方案。也可以從發(fā)生了錯(cuò)誤是否可恢復(fù)來(lái)進(jìn)行分類,例如,C語(yǔ)言中對(duì)可恢復(fù)的錯(cuò)誤會(huì)使用錯(cuò)誤碼返回值,對(duì)不可恢復(fù)的錯(cuò)誤會(huì)直接調(diào)用exit來(lái)退出程序;Java的異常體系分為ExceptionError,分別對(duì)應(yīng)可恢復(fù)錯(cuò)誤和不可恢復(fù)錯(cuò)誤。在Rust中,錯(cuò)誤處理的方案和C語(yǔ)言類似,但更加完善好用:對(duì)于不可恢復(fù)錯(cuò)誤,使用panic來(lái)處理,使得程序直接退出并可輸出相關(guān)信息;對(duì)于可恢復(fù)錯(cuò)誤,使用OptionResult來(lái)對(duì)返回值進(jìn)行封裝,表達(dá)能力更強(qiáng)。

不可恢復(fù)錯(cuò)誤

panic簡(jiǎn)介

對(duì)于不可恢復(fù)錯(cuò)誤,Rust提供了panic機(jī)制來(lái)使得程序迅速崩潰,并報(bào)告相應(yīng)的出錯(cuò)信息。panic出現(xiàn)的場(chǎng)景一般是:如果繼續(xù)執(zhí)行下去就會(huì)有極其嚴(yán)重的內(nèi)存安全問(wèn)題,這種時(shí)候讓程序繼續(xù)執(zhí)行導(dǎo)致的危害比崩潰更嚴(yán)重。舉個(gè)例子:

fn main() {
    let v = vec![1, 2, 3];
    println!("{:?}", v[6]);
}

對(duì)于上面的程序,數(shù)組v有三個(gè)元素,但索引值是6,所以運(yùn)行后程序會(huì)崩潰并報(bào)以下錯(cuò)誤:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 6', src/main.rs:176:22
stack backtrace:
   函數(shù)調(diào)用棧...
panic實(shí)現(xiàn)機(jī)制

在Rust中,panic的實(shí)現(xiàn)機(jī)制有兩種方式:

  • unwind方式:發(fā)生panic時(shí),會(huì)一層一層地退出函數(shù)調(diào)用棧,棧內(nèi)的局部變量還可以正常析構(gòu)。

  • abort方式:發(fā)生panic時(shí),直接退出整個(gè)程序。

默認(rèn)情況下編譯器使用unwind方式,函數(shù)調(diào)用棧信息可以幫助我們快速定位發(fā)生panic的第一現(xiàn)場(chǎng);但某些嵌入式系統(tǒng)因資源不足而只能選擇abort方式,可以通過(guò)rustc -C panic=abort test.rs方式指定。

在Rust中,通過(guò)unwind方式實(shí)現(xiàn)的panic,其內(nèi)部實(shí)現(xiàn)方式基本與C++的異常是一樣的。Rust提供了一些工具函數(shù),可以像try-catch機(jī)制那樣讓用戶在代碼中終止棧展開,例如:

fn main() {
    std::panic::catch_unwind(|| {
        let v = vec![1, 2, 3];
        println!("{:?}", v[6]);
        println!("interrupted"); // 沒(méi)有輸出
    })
    .ok();

    println!("continue"); // 正常輸出
}

運(yùn)行程序可以發(fā)現(xiàn),println!("interrupted");語(yǔ)句沒(méi)有執(zhí)行,因此在上一條語(yǔ)句出發(fā)了panic,這個(gè)函數(shù)調(diào)用棧開始銷毀,但std::panic::catch_unwind阻止了調(diào)用棧的繼續(xù)展開,因此println!("continue");得以正常執(zhí)行。

需要注意的是,不要像try-catch那樣使用catch_unwind來(lái)進(jìn)行流程控制,Rust更推薦基于返回值的錯(cuò)誤處理機(jī)制,因?yàn)榧热话l(fā)生panic了,就讓程序越早崩潰越好,這有利于調(diào)試bug,而使用catch_unwind會(huì)讓錯(cuò)誤暫時(shí)被壓制,從而讓錯(cuò)誤傳遞到其他位置,導(dǎo)致不容易找到程序崩潰的第一現(xiàn)場(chǎng)。catch_unwind主要用于以下兩種情況:

  • 在FFI的場(chǎng)景下,若C語(yǔ)言調(diào)用了Rust的函數(shù),在Rust內(nèi)部出現(xiàn)了panic,如果這個(gè)panic在Rust內(nèi)部沒(méi)處理好,直接扔到C代碼中去,會(huì)導(dǎo)致產(chǎn)生“未定義行為”。

  • 某些高級(jí)抽象機(jī)制需要阻止棧展開,例如線程池。如果一個(gè)線程中出現(xiàn)了panic,我們只希望把這個(gè)線程關(guān)閉,而不是將整個(gè)線程池拖下水。

可恢復(fù)錯(cuò)誤

基本錯(cuò)誤處理

對(duì)于可恢復(fù)的錯(cuò)誤,Rust中提供了基于返回值的方案,主要基于Option<T>Result<T, E>類型。Option<T>代表返回值要么是空要么是非空,Result<T, E>代表返回值要么是正常值的要么錯(cuò)誤值。它們的定義如下:

pub enum Option<T> {
    /// No value
    None,
    /// Some value `T`
    Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}

pub enum Result<T, E> {
    /// Contains the success value
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

我們來(lái)看一個(gè)標(biāo)準(zhǔn)庫(kù)中對(duì)Result<T, E>的典型用法,FromStr中的from_str方法可以通過(guò)字符串構(gòu)造出當(dāng)前類型的實(shí)例,但可能會(huì)構(gòu)造失敗。標(biāo)準(zhǔn)庫(kù)中針對(duì)bool類型實(shí)現(xiàn)了這個(gè)trait,正常情況返回bool類型的值,異常情況返回ParseBoolError類型的值:

pub trait FromStr: Sized {
    /// The associated error which can be returned from parsing.
    type Err;

    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

impl FromStr for bool {
    type Err = ParseBoolError;

    fn from_str(s: &str) -> Result<bool, ParseBoolError> {
        match s {
            "true" => Ok(true),
            "false" => Ok(false),
            _ => Err(ParseBoolError { _priv: () }),
        }
    }
}

我們?cè)賮?lái)看一個(gè)標(biāo)準(zhǔn)庫(kù)中對(duì)Option<T>的典型用法,Iteratornext方法要么返回下一個(gè)元素,要么無(wú)元素可返回,因此使用Option<T>非常合適。

#[must_use = "iterators are lazy and do nothing unless consumed"]
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    ...
}

Option<T>類型解決了許多編程語(yǔ)言中存在的空指針問(wèn)題??罩羔樳@個(gè)設(shè)計(jì)在加入編程語(yǔ)言時(shí)沒(méi)有經(jīng)過(guò)深思熟慮,而只是因?yàn)橐子趯?shí)現(xiàn)而已??罩羔樧畲蟮膯?wèn)題在于,它違背了類型系統(tǒng)的規(guī)定。類型規(guī)定了數(shù)據(jù)可能的取值范圍,規(guī)定了在這些值上可能的操作,也規(guī)定了這些數(shù)據(jù)代表的含義,還規(guī)定了這些數(shù)據(jù)的存儲(chǔ)方式。但是,一個(gè)普通的指針和一個(gè)空指針,哪怕它們是同樣的類型,做同樣的操作,所得到的結(jié)果是不同的。因此,并不能說(shuō)空指針和普通指針是同一個(gè)類型,空指針在類型系統(tǒng)上打開了一個(gè)缺口,引入了一個(gè)必須在運(yùn)行期特殊處理的值,它讓編譯器的類型檢查在此失去了意義。對(duì)此,Rust的解決方案是把空指針null從一個(gè)值上升為一個(gè)類型,用enum類型的Option<T>None來(lái)代表空指針,而Rust中的enum要求在使用時(shí)必須對(duì)enum的每一種可能性都進(jìn)行處理,因此強(qiáng)迫程序員必須考慮到Option<T>None的情形。C/C++中也增添了類似的設(shè)計(jì),但由于前向兼容的問(wèn)題,無(wú)法強(qiáng)制使用,因此其作用也就弱化了很多。

問(wèn)號(hào)運(yùn)算符

Rust中提供了問(wèn)號(hào)運(yùn)算符?語(yǔ)法糖來(lái)簡(jiǎn)化Result<T, E>Option<T>的使用,問(wèn)號(hào)運(yùn)算符的意思是,如果結(jié)果是Err,則提前返回,否則繼續(xù)執(zhí)行。?對(duì)應(yīng)著std::ops::Try這個(gè)trait,編譯器會(huì)把expr?這個(gè)表達(dá)式自動(dòng)轉(zhuǎn)換為以下語(yǔ)義:

match Try::into_result(expr) {
    Ok(V) => v,
    Err(e) => return Try::from_error(From::from(e)),
}

標(biāo)準(zhǔn)庫(kù)中已經(jīng)為Result<T, E>Option<T>兩個(gè)類型實(shí)現(xiàn)了Try

impl<T> ops::Try for Option<T> {
    type Ok = T;
    type Error = NoneError;

    fn into_result(self) -> Result<T, NoneError> {
        self.ok_or(NoneError)
    }

    fn from_ok(v: T) -> Self {
        Some(v)
    }

    fn from_error(_: NoneError) -> Self {
        None
    }
}

impl<T> ops::Try for Option<T> {
    type Ok = T;
    type Error = NoneError;

    fn into_result(self) -> Result<T, NoneError> {
        self.ok_or(NoneError)
    }

    fn from_ok(v: T) -> Self {
        Some(v)
    }

    fn from_error(_: NoneError) -> Self {
        None
    }
}

可以看到,對(duì)于Result類型,執(zhí)行問(wèn)號(hào)運(yùn)算符時(shí),如果碰到Err,則調(diào)用Fromtrait做類型轉(zhuǎn)換,然后中斷當(dāng)前邏輯提前返回。

需要注意的是,問(wèn)號(hào)運(yùn)算符的引入給main函數(shù)帶來(lái)了挑戰(zhàn),因?yàn)閱?wèn)號(hào)運(yùn)算符要求函數(shù)返回值是Result類型,而main函數(shù)是fn() -> ()類型,解決這個(gè)問(wèn)題的辦法就是修改main函數(shù)的簽名類型,但這樣又會(huì)破壞舊代碼。Rust最終的解決方案是引入了一個(gè)trait:

pub trait Termination {
    /// Is called to get the representation of the value as status code.
    /// This status code is returned to the operating system.
    fn report(self) -> i32;
}

impl Termination for () {
    #[inline]
    fn report(self) -> i32 {
        ExitCode::SUCCESS.report()
    }
}

impl<E: fmt::Debug> Termination for Result<(), E> {
    fn report(self) -> i32 {
        match self {
            Ok(()) => ().report(),
            Err(err) => Err::<!, _>(err).report(),
        }
    }
}

impl Termination for ! {
    fn report(self) -> i32 {
        self
    }
}

impl<E: fmt::Debug> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);
        ExitCode::FAILURE.report()
    }
}

impl Termination for ExitCode {
    #[inline]
    fn report(self) -> i32 {
        self.0.as_i32()
    }
}

main函數(shù)的簽名就對(duì)應(yīng)地改成了fn<T: Termination>() -> T,標(biāo)準(zhǔn)庫(kù)為Result類型、()類型等都實(shí)現(xiàn)了這個(gè)trait,從而這些類型都可以作為main函數(shù)的返回類型了。

到此,關(guān)于“Rust錯(cuò)誤處理有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI