rust盖错了怎么拆除_细说Rust错误处理
細(xì)說Rust錯誤處理
1. 前言
這篇文章寫得比較長,全文讀完大約需要15-20min,如果對Rust的錯誤處理不清楚或還有些許模糊的同學(xué),請靜下心來細(xì)細(xì)閱讀。當(dāng)讀完該篇文章后,可以說對Rust的錯誤處理可以做到掌握自如。
筆者花費(fèi)較長篇幅來描述錯誤處理的來去,詳細(xì)介紹其及一步步梳理內(nèi)容,望大家能耐心讀完后對大家有所幫助。當(dāng)然,在寫這篇文章之時,也借閱了大量互聯(lián)網(wǎng)資料,詳見鏈接見底部參考鏈接
掌握好Rust的錯誤設(shè)計(jì),不僅可以提升我們對錯誤處理的認(rèn)識,對代碼結(jié)構(gòu)、層次都有很大的幫助。那廢話不多說,那我們開啟這段閱讀之旅吧 !
2. 背景
筆者在寫這篇文章時,也翻閱一些資料關(guān)于Rust的錯誤處理資料,多數(shù)是對其一筆帶過,導(dǎo)致之前接觸過其他語言的新同學(xué)來說,上手處理Rust的錯誤會有當(dāng)頭棒喝的感覺。找些資料發(fā)現(xiàn)unwrap()也可以解決問題,然后心中暗自竊喜,程序在運(yùn)行過程中,因?yàn)楹雎詸z查或程序邏輯判斷,導(dǎo)致某些情況,程序panic。這可能是我們最不愿看到的現(xiàn)象,遂又回到起點(diǎn),重新去了解Rust的錯誤處理。
這篇文章,通過一步步介紹,讓大家清晰知道Rust的錯誤處理的究竟。介紹在Rust中的錯誤使用及如何處理錯誤,以及在實(shí)際工作中關(guān)于其使用技巧。
3. unwrap的危害!
下面我們來看一段代碼,執(zhí)行一下:
fn main(){letpath="/tmp/dat";println!("{}",read_file(path));}fn read_file(path: &str)-> String {std::fs::read_to_string(path).unwrap()}
程序執(zhí)行結(jié)果:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:1188:5
stack backtrace:
0: backtrace::backtrace::libunwind::trace
at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88
...
15: rust_sugar::read_file
at src/main.rs:7
16: rust_sugar::main
at src/main.rs:3
...
25: rust_sugar::read_file
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
什么,因?yàn)閜ath路徑不對,程序竟然崩潰了,這個是我們不能接受的!
unwrap() 這個操作在rust代碼中,應(yīng)該看過很多這種代碼,甚至此時我們正在使用它。它主要用于Option或Result的打開其包裝的結(jié)果。常常我們在代碼中,使用簡單,或快速處理,使用了 unwrap() 的操作,但是,它是一個非常危險的信號!
可能因?yàn)闆]有程序檢查或校驗(yàn),潛在的bug可能就出現(xiàn)其中,使得我們程序往往就panic了。這可能使我們最不愿看到的現(xiàn)象。
在實(shí)際項(xiàng)目開發(fā)中,程序中可能充斥著大量代碼,我們很難避免unwrap()的出現(xiàn),為了解決這種問題,我們通過做code review,或使用腳本工具檢查其降低其出現(xiàn)的可能性。
通常每個項(xiàng)目都有一些約束,或許:在大型項(xiàng)目開發(fā)中, 不用unwrap() 方法,使用其他方式處理程序,unwrap() 的不出現(xiàn)可能會使得程序的健壯性高出很多。
這里前提是團(tuán)隊(duì)或大型項(xiàng)目,如果只是寫一個簡單例子(demo)就不在本篇文章的討論范疇。因?yàn)橐粋€Demo的問題,可能只是快速示范或演示,不考慮程序健壯性, unwrap() 的操作可能會更方便代碼表達(dá)。
可能有人會問,我們通常跑程序unit test,其中的很多mock數(shù)據(jù)會有 unwrap() 的操作,我們只是為了在單元測試中使得程序簡單。這種也能不使用嗎?答案:是的,完全可以不使用 unwrap() 也可以做到的。
4. 對比語言處理錯誤
說到unwrap(),我們不得不提到rust的錯誤處理,unwrap() 和Rust的錯誤處理是密不可分的。
4.1 golang的錯誤處理演示
如果了解golang的話,應(yīng)該清楚下面這段代碼的意思:
package main
import (
"io/ioutil"
"log"
)
func main() {
path := "/tmp/dat" //文件路徑 file, err := readFile(path)
if err != nil {
log.Fatal(err) //錯誤打印 }
println("%s", file) //打印文件內(nèi)容}
func readFile(path string) (string, error) {
dat, err := ioutil.ReadFile(path) //讀取文件內(nèi)容 if err != nil { //判斷err是否為nil return "", err //不為nil,返回err結(jié)果 }
return string(dat), nil //err=nil,返回讀取文件內(nèi)容}
我們執(zhí)行下程序,打印如下。執(zhí)行錯誤,當(dāng)然,因?yàn)槲覀兘o的文件路徑不存在,程序報錯。
2020/02/24 01:24:04 open /tmp/dat: no such file or directory
這里,golang采用多返回值方式,程序報錯返回錯誤問題,通過判斷 err!=nil 來決定程序是否繼續(xù)執(zhí)行或終止該邏輯。當(dāng)然,如果接觸過golang項(xiàng)目時,會發(fā)現(xiàn)程序中大量充斥著if err!=nil的代碼,對此網(wǎng)上有對if err!=nil進(jìn)行了很多討論,因?yàn)檫@個不在本篇文章的范疇中,在此不對其追溯、討論。
4.2 Rust 錯誤處理示例
對比了golang代碼,我們對照上面的例子,看下在Rust中如何編寫這段程序,代碼如下:
fn main(){letpath="/tmp/dat";//文件路徑matchread_file(path){//判斷方法結(jié)果Ok(file)=>{println!("{}",file)}//OK 代表讀取到文件內(nèi)容,正確打印文件內(nèi)容Err(e)=>{println!("{} {}",path,e)}//Err代表結(jié)果不存在,打印錯誤結(jié)果}}fn read_file(path: &str)-> Result{//Result作為結(jié)果返回值std::fs::read_to_string(path)//讀取文件內(nèi)容}
當(dāng)前,因?yàn)槲覀兘o的文件路徑不存在,程序報錯,打印內(nèi)容如下:
No such file or directory (os error 2)
在Rust代表中,Result是一個enum枚舉對象,部分源碼如下:
pubenum Result{/// Contains the success valueOk(#[stable(feature ="rust1", since ="1.0.0")]T),/// Contains the error valueErr(#[stable(feature ="rust1", since ="1.0.0")]E),}
通常我們使用Result的枚舉對象作為程序的返回值,通過Result來判斷其結(jié)果,我們使用match匹配的方式來獲取Result的內(nèi)容,判斷正常(Ok)或錯誤(Err)。
或許,我們大致向上看去,golang代碼和Rust代碼沒有本質(zhì)區(qū)別,都是采用返回值方式,給出程序結(jié)果。下面我們就對比兩種語言說說之間區(qū)別:golang采用多返回值方式,我們在拿到目標(biāo)結(jié)果時(上面是指文件內(nèi)容file),需要首先對err判斷是否為nil,并且我們在return時,需要給多返回值分別賦值,調(diào)用時需要對 if err!=nil 做結(jié)果判斷。
Rust中采用Result的枚舉對象做結(jié)果返回。枚舉的好處是:多選一。因?yàn)镽esult的枚舉類型為Ok和Err,使得我們每次在返回Result的結(jié)果時,要么是Ok,要么是Err。它不需要return結(jié)果同時給兩個值賦值,這樣的情況只會存在一種可能性: Ok or Err 。
golang的函數(shù)調(diào)用需要對 if err!=nil做結(jié)果判斷,因?yàn)檫@段代碼 判斷是手動邏輯,往往我們可能因?yàn)槭韬?#xff0c;導(dǎo)致這段邏輯缺失,缺少校驗(yàn)。當(dāng)然,我們在編寫代碼期間可以通過某些工具 lint 掃描出這種潛在bug。
Rust的match判斷是自動打開,當(dāng)然你也可以選擇忽略其中某一個枚舉值,我們不在此說明。
可能有人發(fā)現(xiàn),如果我有多個函數(shù),需要多個函數(shù)的執(zhí)行結(jié)果,這樣需要match代碼多次,代碼會不會是一坨一坨,顯得代碼很臃腫,難看。是的,這個問題提出的的確是有這種問題,不過這個在后面我們講解的時候,會通過程序語法糖避免多次match多次結(jié)果的問題,不過我們在此先不敘說,后面將有介紹。
5. Rust中的錯誤處理
前面不管是golang還是Rust采用return返回值方式,兩者都是為了解決程序中錯誤處理的問題。好了,前面說了這么多,我們還是回歸正題:Rust中是如何對錯誤進(jìn)行處理的?
要想細(xì)致了解Rust的錯誤處理,我們需要了解std::error::Error,該trait的內(nèi)部方法,部分代碼如下: 參考鏈接:https://doc.rust-lang.org/std/error/trait.Error.html
pubtraitError: Debug+Display{fn description(&self)-> &str {"description() is deprecated; use Display"}#[rustc_deprecated(since ="1.33.0", reason ="replaced by Error::source, which can support \downcasting")]fn cause(&self)-> Option{self.source()}fn source(&self)-> Option{None}#[doc(hidden)]fn type_id(&self,_: private::Internal)-> TypeIdwhereSelf: 'static{TypeId::of::()}#[unstable(feature ="backtrace", issue ="53487")]fn backtrace(&self)-> Option{None}}description()在文檔介紹中,盡管使用它不會導(dǎo)致編譯警告,但新代碼應(yīng)該實(shí)現(xiàn)impl Display ,新impl的可以省略,不用實(shí)現(xiàn)該方法, 要獲取字符串形式的錯誤描述,請使用to_string()。
cause()在1.33.0被拋棄,取而代之使用source()方法,新impl的不用實(shí)現(xiàn)該方法。
source()此錯誤的低級源,如果內(nèi)部有錯誤類型Err返回:Some(e),如果沒有返回:None。
如果當(dāng)前Error是低級別的Error,并沒有子Error,需要返回None。介于其本身默認(rèn)有返回值None,可以不覆蓋該方法。
如果當(dāng)前Error包含子Error,需要返回子Error:Some(err),需要覆蓋該方法。
type_id()該方法被隱藏。
backtrace()返回發(fā)生此錯誤的堆棧追溯,因?yàn)闃?biāo)記unstable,在Rust的stable版本不被使用。
自定義的Error需要impl std::fmt::Debug的trait,當(dāng)然我們只需要在默認(rèn)對象上添加注解:#[derive(Debug)]即可。
總結(jié)一下,自定義一個error需要實(shí)現(xiàn)如下幾步:手動實(shí)現(xiàn)impl std::fmt::Display的trait,并實(shí)現(xiàn) fmt(...)方法。
手動實(shí)現(xiàn)impl std::fmt::Debug的trait,一般直接添加注解即可:#[derive(Debug)]
手動實(shí)現(xiàn)impl std::error::Error的trait,并根據(jù)自身error級別是否覆蓋std::error::Error中的source()方法。
下面我們自己手動實(shí)現(xiàn)下Rust的自定義錯誤:CustomError
usestd::error::Error;///自定義類型 Error,實(shí)現(xiàn)std::fmt::Debug的trait#[derive(Debug)]struct CustomError{err: ChildError,}///實(shí)現(xiàn)Display的trait,并實(shí)現(xiàn)fmt方法implstd::fmt::DisplayforCustomError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{write!(f,"CustomError is here!")}}///實(shí)現(xiàn)Error的trait,因?yàn)橛凶覧rror:ChildError,需要覆蓋source()方法,返回Some(err)implstd::error::ErrorforCustomError{fn source(&self)-> Option{Some(&self.err)}}///子類型 Error,實(shí)現(xiàn)std::fmt::Debug的trait#[derive(Debug)]struct ChildError;///實(shí)現(xiàn)Display的trait,并實(shí)現(xiàn)fmt方法implstd::fmt::DisplayforChildError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{write!(f,"ChildError is here!")}}///實(shí)現(xiàn)Error的trait,因?yàn)闆]有子Error,不需要覆蓋source()方法implstd::error::ErrorforChildError{}///構(gòu)建一個Result的結(jié)果,返回自定義的error:CustomErrorfn get_super_error()-> Result{Err(CustomError{err: ChildError})}fn main(){matchget_super_error(){Err(e)=>{println!("Error: {}",e);println!("Caused by: {}",e.source().unwrap());}_=>println!("No error"),}}ChildError為子類型Error,沒有覆蓋source()方法,空實(shí)現(xiàn)了std::error::Error
CustomError有子類型ChildError,覆蓋了source(),并返回了子類型Option值:Some(&self.err)
運(yùn)行執(zhí)行結(jié)果,顯示如下:
Error: CustomError is here!
Caused by: ChildError is here!
至此,我們就了解了如何實(shí)現(xiàn)Rust中自定義Error了。
6. 自定義Error轉(zhuǎn)換:From
上面我們說到,函數(shù)返回Result的結(jié)果時,需要獲取函數(shù)的返回值是成功(Ok)還是失敗(Err),需要使用match匹配,我們看下多函數(shù)之間調(diào)用是如何解決這類問題的?假設(shè)我們有個場景: 讀取一文件 將文件內(nèi)容轉(zhuǎn)化為UTF8格式 * 將轉(zhuǎn)換后格式內(nèi)容轉(zhuǎn)為u32的數(shù)字。
所以我們有了下面三個函數(shù)(省略部分代碼):
...///讀取文件內(nèi)容fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}/// 轉(zhuǎn)換為utf8內(nèi)容fn to_utf8(v: &[u8])-> Result{std::str::from_utf8(v)}/// 轉(zhuǎn)化為u32數(shù)字fn to_u32(v: &str)-> Result{v.parse::()}
最終,我們得到u32的數(shù)字,對于該場景如何組織我們代碼呢?unwrap()直接打開三個方法,取出值。這種方式太暴力,并且會有bug,造成程序panic,不被采納。
match匹配,如何返回OK,繼續(xù)下一步,否則報錯終止邏輯,那我們試試。
參考代碼如下:
fn main(){letpath="./dat";matchread_file(path){Ok(v)=>{matchto_utf8(v.as_bytes()){Ok(u)=>{matchto_u32(u){Ok(t)=>{println!("num:{:?}",u);}Err(e)=>{println!("{} {}",path,e)}}}Err(e)=>{println!("{} {}",path,e)}}}Err(e)=>{println!("{} {}",path,e)}}}///讀取文件內(nèi)容fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}/// 轉(zhuǎn)換為utf8內(nèi)容fn to_utf8(v: &[u8])-> Result{std::str::from_utf8(v)}/// 轉(zhuǎn)化為u32數(shù)字fn to_u32(v: &str)-> Result{v.parse::()}
天啊,雖然是實(shí)現(xiàn)了上面場景的需求,但是代碼猶如疊羅漢,程序結(jié)構(gòu)越來越深啊,這個是我們沒法接受的!match匹配導(dǎo)致程序如此不堪一擊。那么有沒有第三種方法呢?當(dāng)然是有的:From轉(zhuǎn)換。
前面我們說到如何自定義的Error,如何我們將上面三個error收納到我們自定義的Error中,將它們?nèi)齻€Error變成自定義Error的子Error,這樣我們對外的Result統(tǒng)一返回自定義的Error。這樣程序應(yīng)該可以改變點(diǎn)什么,我們來試試吧。
#[derive(Debug)]enum CustomError{ParseIntError(std::num::ParseIntError),Utf8Error(std::str::Utf8Error),IoError(std::io::Error),}implstd::error::ErrorforCustomError{fn source(&self)-> Option{match&self{CustomError::IoError(refe)=>Some(e),CustomError::Utf8Error(refe)=>Some(e),CustomError::ParseIntError(refe)=>Some(e),}}}implDisplayforCustomError{fn fmt(&self,f: &mutFormatter)-> std::fmt::Result{match&self{CustomError::IoError(refe)=>e.fmt(f),CustomError::Utf8Error(refe)=>e.fmt(f),CustomError::ParseIntError(refe)=>e.fmt(f),}}}implFromforCustomError{fn from(s: std::num::ParseIntError)-> Self{CustomError::ParseIntError(s)}}implFromforCustomError{fn from(s: std::io::Error)-> Self{CustomError::IoError(s)}}implFromforCustomError{fn from(s: std::str::Utf8Error)-> Self{CustomError::Utf8Error(s)}}CustomError為我們實(shí)現(xiàn)的自定義Error
CustomError有三個子類型Error
CustomError分別實(shí)現(xiàn)了三個子類型Error From的trait,將其類型包裝為自定義Error的子類型
好了,有了自定義的CustomError,那怎么使用呢? 我們看代碼:
usestd::io::ErrorasIoError;usestd::str::Utf8Error;usestd::num::ParseIntError;usestd::fmt::{Display,Formatter};fn main()-> std::result::Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;println!("num:{:?}",u);Ok(())}///讀取文件內(nèi)容fn read_file(path: &str)-> std::result::Result{std::fs::read_to_string(path)}/// 轉(zhuǎn)換為utf8內(nèi)容fn to_utf8(v: &[u8])-> std::result::Result{std::str::from_utf8(v)}/// 轉(zhuǎn)化為u32數(shù)字fn to_u32(v: &str)-> std::result::Result{v.parse::()}#[derive(Debug)]enum CustomError{ParseIntError(std::num::ParseIntError),Utf8Error(std::str::Utf8Error),IoError(std::io::Error),}implstd::error::ErrorforCustomError{fn source(&self)-> Option{match&self{CustomError::IoError(refe)=>Some(e),CustomError::Utf8Error(refe)=>Some(e),CustomError::ParseIntError(refe)=>Some(e),}}}implDisplayforCustomError{fn fmt(&self,f: &mutFormatter)-> std::fmt::Result{match&self{CustomError::IoError(refe)=>e.fmt(f),CustomError::Utf8Error(refe)=>e.fmt(f),CustomError::ParseIntError(refe)=>e.fmt(f),}}}implFromforCustomError{fn from(s: std::num::ParseIntError)-> Self{CustomError::ParseIntError(s)}}implFromforCustomError{fn from(s: std::io::Error)-> Self{CustomError::IoError(s)}}implFromforCustomError{fn from(s: std::str::Utf8Error)-> Self{CustomError::Utf8Error(s)}}
其實(shí)我們主要關(guān)心的是這段代碼:
fn main()-> Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;println!("num:{:?}",u);Ok(())}
我們使用了?來替代原來的match匹配的方式。?使用問號作用在函數(shù)的結(jié)束,意思是:程序接受了一個Result自定義的錯誤類型。
當(dāng)前如果函數(shù)結(jié)果錯誤,程序自動拋出Err自身錯誤類型,并包含相關(guān)自己類型錯誤信息,因?yàn)槲覀冏隽薋rom轉(zhuǎn)換的操作,該函數(shù)的自身類型錯誤會通過實(shí)現(xiàn)的From操作自動轉(zhuǎn)化為CustomError的自定義類型錯誤。
當(dāng)前如果函數(shù)結(jié)果正確,繼續(xù)之后邏輯,直到程序結(jié)束。
這樣,我們通過From和?解決了之前match匹配代碼層級深的問題,因?yàn)檫@種轉(zhuǎn)換是無感知的,使得我們在處理好錯誤類型后,只需要關(guān)心我們的目標(biāo)值即可,這樣不需要顯示對Err(e)的數(shù)據(jù)單獨(dú)處理,使得我們在函數(shù)后添加?后,程序一切都是自動了。
還記得我們之前討論在對比golang的錯誤處理時的:if err!=nil的邏輯了嗎,這種因?yàn)橛昧?語法糖使得該段判斷將不再存在。
另外,我們還注意到,Result的結(jié)果可以作用在main函數(shù)上,是的,Result的結(jié)果不僅能作用在main函數(shù)上
Result還可以作用在單元測試上,這就是我們文中剛開始提到的:因?yàn)橛辛薘esult的作用,使得我們在程序中幾乎可以完全摒棄unwrap()的代碼塊,使得程序更輕,大大減少潛在問題,程序組織結(jié)構(gòu)更加清晰。
下面這是作用在單元測試上的Result的代碼:
...#[cfg(test)]mod tests{usesuper::*;#[test]fn test_get_num()-> std::result::Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;assert_eq!(u,8);Ok(())}}
7. 重命名Result
我們在實(shí)際項(xiàng)目中,會大量使用如上的Result結(jié)果,并且Result的Err類型是我們自定義錯誤,導(dǎo)致我們寫程序時會顯得非常啰嗦、冗余
///讀取文件內(nèi)容fn read_file(path: &str)-> std::result::Result{letval=std::fs::read_to_string(path)?;Ok(val)}/// 轉(zhuǎn)換為utf8內(nèi)容fn to_utf8(v: &[u8])-> std::result::Result{letx=std::str::from_utf8(v)?;Ok(x)}/// 轉(zhuǎn)化為u32數(shù)字fn to_u32(v: &str)-> std::result::Result{leti=v.parse::()?;Ok(i)}
我們的程序中,會大量充斥著這種模板代碼,Rust本身支持對類型自定義,使得我們只需要重命名Result即可:
pubtype IResult=std::result::Result;///自定義Result類型:IResult
這樣,凡是使用的是自定義類型錯誤的Result都可以使用IResult來替換std::result::Result的類型,使得簡化程序,隱藏Error類型及細(xì)節(jié),關(guān)注目標(biāo)主體,代碼如下:
///讀取文件內(nèi)容fn read_file(path: &str)-> IResult{letval=std::fs::read_to_string(path)?;Ok(val)}/// 轉(zhuǎn)換為utf8內(nèi)容fn to_utf8(v: &[u8])-> IResult{letx=std::str::from_utf8(v)?;Ok(x)}/// 轉(zhuǎn)化為u32數(shù)字fn to_u32(v: &str)-> IResult{leti=v.parse::()?;Ok(i)}
將std::result::Result 替換為:IResult類型
當(dāng)然,會有人提問,如果是多參數(shù)類型怎么處理呢,同樣,我們只需將OK類型變成 tuple (I,O)類型的多參數(shù)數(shù)據(jù)即可,大概這樣:
pubtype IResult=std::result::Result;
使用也及其簡單,只需要返回:I,O的具體類型,舉個示例:
fn foo()-> IResult{Ok((String::from("bar"),32))}
使用重命名類型的Result,使得我們錯誤類型統(tǒng)一,方便處理。在實(shí)際項(xiàng)目中,可以大量看到這種例子的存在。
8. Option轉(zhuǎn)換
我們知道,在Rust中,需要使用到unwrap()的方法的對象有Result,Option對象。我們看下Option的大致結(jié)構(gòu):
pubenum Option{/// No value#[stable(feature ="rust1", since ="1.0.0")]None,/// Some value `T`#[stable(feature ="rust1", since ="1.0.0")]Some(#[stable(feature ="rust1", since ="1.0.0")]T),}
Option本身是一個enum對象,如果該函數(shù)(方法)調(diào)用結(jié)果值沒有值,返回None,反之有值返回Some(T)
如果我們想獲取Some(T)中的T,最直接的方式是:unwrap()。我們前面說過,使用unwrap()的方式太過于暴力,如果出錯,程序直接panic,這是我們最不愿意看到的結(jié)果。
Ok,那么我們試想下, 利用Option能使用?語法糖嗎?如果能用?轉(zhuǎn)換的話,是不是代碼結(jié)構(gòu)就更簡單了呢?我們嘗試下,代碼如下:
#[derive(Debug)]enum Error{OptionError(String),}implstd::error::ErrorforError{}implstd::fmt::DisplayforError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{match&self{Error::OptionError(refe)=>e.fmt(f),}}}pubtype Result=std::result::Result;fn main()-> Result{letbar=foo(60)?;assert_eq!("bar",bar);Ok(())}fn foo(index: i32)-> Option{ifindex>60{returnSome("bar".to_string());}None}
執(zhí)行結(jié)果報錯:
error[E0277]: `?` couldn't convert the error to `Error`
--> src/main.rs:22:22
|
22 | let bar = foo(60)?;
| ^ the trait `std::convert::From<:option::noneerror>` is not implemented for `Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= note: required by `std::convert::From::from`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `hyper-define`.
提示告訴我們沒有轉(zhuǎn)換std::convert::From<:option::noneerror>,但是NoneError本身是unstable,這樣我們沒法通過From轉(zhuǎn)換為自定義Error。
本身,在Rust的設(shè)計(jì)中,關(guān)于Option和Result就是一對孿生兄弟一樣的存在,Option的存在可以忽略異常的細(xì)節(jié),直接關(guān)注目標(biāo)主體。當(dāng)然,Option也可以通過內(nèi)置的組合器ok_or()方法將其變成Result。我們大致看下實(shí)現(xiàn)細(xì)節(jié):
implOption{pubfn ok_or(self,err: E)-> Result{matchself{Some(v)=>Ok(v),None=>Err(err),}}}
這里通過ok_or()方法通過接收一個自定義Error類型,將一個Option->Result。好的,變成Result的類型,我們就是我們熟悉的領(lǐng)域了,這樣處理起來就很靈活。
關(guān)于Option的其他處理方式,不在此展開解決,詳細(xì)的可看下面鏈接:
9. 避免unwrap()
有人肯定會有疑問,如果需要判斷的邏輯,又不用?這種操作,怎么取出Option或Result的數(shù)據(jù)呢,當(dāng)然點(diǎn)子總比辦法多,我們來看下Option如何做的:
fn main(){ifletSome(v)=opt_val(60){println!("{}",v);}}fn opt_val(num: i32)-> Option{ifnum>=60{returnSome("foo bar".to_string());}None}
是的,我們使用if let Some(v)的方式取出值,當(dāng)前else的邏輯就可能需要自己處理了。當(dāng)然,Option可以這樣做,Result也一定可以:
fn main(){ifletOk(v)=read_file("./dat"){println!("{}",v);}}fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}
只不過,在處理Result的判斷時,使用的是if let Ok(v),這個和Option的if let Some(v)有所不同。
到這里,unwrap()的代碼片在項(xiàng)目中應(yīng)該可以規(guī)避了。補(bǔ)充下,這里強(qiáng)調(diào)了幾次規(guī)避,就如前所言:團(tuán)隊(duì)風(fēng)格統(tǒng)一,方便管理代碼,消除潛在危機(jī)。
10. 自定義Error同級轉(zhuǎn)換
我們在項(xiàng)目中,一個函數(shù)(方法)內(nèi)部會有多次Result的結(jié)果判斷:?,假設(shè)我們自定義的全局Error名稱為:GlobalError。
這時候,如果全局有一個Error可能就會出現(xiàn)如下錯誤:
std::convert::From<:globalerror>>`isnotimplementedfor`error::GlobalError
意思是:我們自定義的GlobalError沒有通過From>轉(zhuǎn)換我們自己自定義的GlobalError,那這樣,就等于自己轉(zhuǎn)換自己。注意:第一:這是我們不期望這樣做的。
第二:遇到這種自己轉(zhuǎn)換自己的T類型很多,我們不可能把出現(xiàn)的T類型通通實(shí)現(xiàn)一遍。 這時候,我們考慮自定義另一個Error了,假設(shè)我們視為:InnnerError,我們?nèi)值腅rror取名為:GlobalError,我們在遇到上面錯誤時,返回Result,這樣我們遇到Result時,只需要通過From轉(zhuǎn)換即可,代碼示例如下:
implFromforGlobalError{fn from(s: InnerError)-> Self{Error::new(ErrorKind::InnerError(e))}}
上面說的這種情況,可能會在項(xiàng)目中出現(xiàn)多個自定義Error,出現(xiàn)這種情況時,存在多個不同Error的std::result::Result的返回。這里的Err就可以根據(jù)我們業(yè)務(wù)現(xiàn)狀分別反回不同類型了。最終,只要實(shí)現(xiàn)了From的trait可轉(zhuǎn)化為最終期望結(jié)果。
11. Error常見開源庫
好了,介紹到這里,我們應(yīng)該有了非常清晰的認(rèn)知:關(guān)于如何處理Rust的錯誤處理問題了。但是想想上面的這些邏輯多數(shù)是模板代碼,我們在實(shí)際中,大可不必這樣。說到這里,開源社區(qū)也有了很多對錯誤處理庫的支持,下面列舉了一些:
12. 參考鏈接
13 錯誤處理實(shí)戰(zhàn)
這個例子介紹了如何在https://github.com/Geal/nom中處理錯誤,這里就不展開介紹了,有興趣的可自行閱讀代碼。
14. 總結(jié)
好了,經(jīng)過上面的長篇大論,不知道大家是否明白如何自定義處理Error呢了。大家現(xiàn)在帶著之前的已有的問題或困惑,趕緊實(shí)戰(zhàn)下Rust的錯誤處理吧,大家有疑問或者問題都可以留言我,希望這篇文章對你有幫助。
總結(jié)
以上是生活随笔為你收集整理的rust盖错了怎么拆除_细说Rust错误处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: y电容如何选型_干式试验变压器选型依据
- 下一篇: cygwin 远程连接linux,Cyg