AD19 add pins to nets错误_为什么我认为Rust的Result错误处理方式不如Exception
生活随笔
收集整理的這篇文章主要介紹了
AD19 add pins to nets错误_为什么我认为Rust的Result错误处理方式不如Exception
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
由于是對(duì)技術(shù)的個(gè)人評(píng)判,歡迎理性討論。
我曾經(jīng)也當(dāng)過純函數(shù)式的腦殘粉,認(rèn)為宇宙第一棒的代數(shù)數(shù)據(jù)結(jié)構(gòu)用來處理錯(cuò)誤,是無上的優(yōu)雅和絕對(duì)的安全。一個(gè)看似人畜無害的接口拋出異常帶來的崩潰,是各類疑難雜癥的罪魁禍?zhǔn)住>C合起來,sum 類型相比 exception 的優(yōu)勢(shì)有:
- 不可變數(shù)據(jù)結(jié)構(gòu),purity is dignity;
- 編譯檢查,必須處理,懶鬼退散,nobody can avoid it(異常經(jīng)常不被妥善處理,直至發(fā)酵);
- 可以寫在接口聲明中,并且嚴(yán)格規(guī)定了能產(chǎn)生的錯(cuò)誤類型,anyone can hear the lion in the box(幾乎所有使用異常機(jī)制的語言都不要求標(biāo)注異常,且一個(gè)函數(shù)可能拋出任何預(yù)料之外類型的異常);
- 恢復(fù)錯(cuò)誤時(shí)沒有exception那么大的開銷。可以用清晰簡潔的方法處理結(jié)果(unwrap, expect 等臨時(shí)方法,以及 and, or 等 combinator),并且能清楚地區(qū)分對(duì)待嚴(yán)重故障(panic)和預(yù)料之內(nèi)的小問題(相對(duì)而言,捕捉異常要寫大堆 try ... catch 方塊,并且 exception 和 panic 一樣直接崩潰)。
然而我越去實(shí)際使用這兩種方法,越發(fā)覺得 exception 要遠(yuǎn)好于 sum type。原因如下:
- 不強(qiáng)制要求處理異常,不是異常機(jī)制的問題,而是編譯器設(shè)計(jì)的問題。編譯器可以要求拋出異常的函數(shù)標(biāo)注其拋出的異常類型,且調(diào)用這種函數(shù)的函數(shù)如果不處理這些異常,也必須標(biāo)注這些異常的類型;
- 如果強(qiáng)制處理異常,程序員就會(huì)抱怨 try ... catch 太多太煩。但這個(gè)麻煩也完全是設(shè)計(jì)問題。學(xué)習(xí) Rust,同樣可以設(shè)計(jì)?運(yùn)算符,用例如 foo()?,在 foo() 未拋出異常時(shí)直接使用結(jié)果,否則拋出異常給上層。unwrap, except之類的操作符也很容易實(shí)現(xiàn),combinator 也不在話下;
- 代碼量大了,到處都是 Result<T, E> 很讓人崩潰。很多很簡單的功能因?yàn)橐m應(yīng)其內(nèi)部調(diào)用的函數(shù)或外部調(diào)用它的函數(shù),也不得不給返回類型加上 Result。雖然為了安全,這是必要的開銷,但是這里面暗藏兩個(gè)問題:
- 第1個(gè),多寫那么多 Result,并不能在錯(cuò)誤出現(xiàn)時(shí)讓我獲得更多。層層傳遞的 Result不會(huì)自動(dòng)保存調(diào)用鏈條,無法像 exception 那樣從最深處 propogate(浮現(xiàn)?),保存直達(dá)病灶的堆棧信息。所以到需要輸出問題的時(shí)候,Result 只有一行,exception 有幾百行(或許編譯器也可以給Result做優(yōu)化);
- 第2個(gè),Result 的 err type 實(shí)際上自縛手腳,把函數(shù)里可能產(chǎn)生的錯(cuò)誤類型勒死在 err type 上。有時(shí)函數(shù)可能產(chǎn)生多種錯(cuò)誤,卻非要用一個(gè)單獨(dú)的錯(cuò)誤來統(tǒng)一,而這個(gè)單獨(dú)錯(cuò)誤還得起一個(gè)面面俱到的名字,最后名字變得極為抽象,不明所以,最后就統(tǒng)一一種了事(雖然經(jīng)常是工程設(shè)計(jì)問題,但很多時(shí)候身不由己)。相比而言,拋出何種 exception 完全由各個(gè)功能模塊自己決定,不需要相互約束。IO 產(chǎn)生的錯(cuò)誤到外面還是 IOError,沒有轉(zhuǎn)換的開銷。
- 最后一點(diǎn),用了 Result,函數(shù)簽名不再純凈,我的工廠生產(chǎn)的啤酒不再是啤酒,而是Result<啤酒, 生產(chǎn)錯(cuò)誤>,做出來的菜不再是菜,而是 Result<菜, 失敗料理>。再也不能揮揮灑灑寫邏輯,思考也受處理 Result 阻礙。
所以我的提法是,不要拋棄異常。甚至,在有方便的異常處理操作符,且編譯器嚴(yán)格要求程序去處理之時(shí),可以完全不需要 Result。作為例子,看以下 Rust 代碼,它是一個(gè)語法分析程序:
enum Token {/* 定義詞法分析的結(jié)果:token */ }enum Expr {/* 定義語法分析的結(jié)果:表達(dá)式 */ }/* 包含多種錯(cuò)誤,但嚴(yán)格來說 EOF 并不算詞法錯(cuò)誤 */ enum LexError {EOF,UnexpectedEOF,Lexeme(String), }/* 從源碼獲取下一個(gè) token */ fn next_token(source: &str) -> Result<Token, LexError> { /* ... */ }/* 不得不將 LexError 整合進(jìn)來 */ enum ParseError {EOF,UnexpectedEOF,Syntax(String), }/* 從源碼解析一個(gè)表達(dá)式,看起來還挺優(yōu)雅,但有轉(zhuǎn)換開銷 */ fn parse(source: &str) -> Result<Expr, ParseError> {/* ... */match next_token(source) {Ok(token) => /* ... */,Err(LexError::EOF) => Err(ParseError::EOF),Err(LexError::UnexpectedEOF) => Err(ParseError::UnexpectedEOF),Err(LexError::Lexeme(msg)) => Err(ParseError::Syntax(msg)),} }/* 事情剛開始麻煩起來 */ enum ParseFileError {Syntax(ParseError),IO(IOError), }/* 從源文件路徑讀取源碼并解析成表達(dá)式,混入了 io::Error,可以看到判斷邏輯已十分復(fù)雜,很多時(shí)候程序員都直接 unwrap 了事 */ fn parseFile(path: &str) -> Result<Expr, ParseFileError> {let mut file = std::fs::File::open(path);if let Err(ioError) = file {return ParseFileError::IO(ioError);}let mut source = String::new();if let Err(ioError) = file.read_to_string(&mut contents) {return ParseFileError::IO(ioError);}let expr = parse(&source[..]);if let Err(parseError) = expr {return ParseFileError::Syntax(parseError);}return expr.unwrap(); }再來看有嚴(yán)格的 exception 會(huì)怎樣:
enum Token {/* 定義詞法分析的結(jié)果:token */ }enum Expr {/* 定義語法分析的結(jié)果:表達(dá)式 */ }/* 可以任意定義多種異常 */ #[derive(Exception)] struct EOF;#[derive(Exception)] struct UnexpectedEOF;#[derive(Exception)] struct LexError(String);#[derive(Exception)] struct SyntaxError(String);/* 返回類型變?yōu)閱渭兊?Token,加上異常標(biāo)注 */ fn next_token(source: &str) -> Tokenthrows EOF, UnexpectedEOF, LexError { /* ... */ }/* 返回類型變?yōu)閱渭兊?Expr,并且只需處理有必要處理的 next_token 拋出的異常 */ fn parse(source: &str) -> Exprthrows EOF, UnexpectedEOF, SyntaxError {/* ... */let token = next_token(source) except {LexError(msg) => throw SyntaxError(msg),_ => throw _,} }/* io::Error 除了要標(biāo)注,可以完全不用管,清爽很多。再見了,unwrap! */ fn parseFile(path: &str) -> Exprthrows EOF, UnexpectedEOF, SyntaxError, io::Error {let mut file = std::fs::File::open(path)?;let mut source = String::new();file.read_to_string(&mut contents)?;let expr = parse(&source[..])?;return expr; }意義不言自明。
更新,感謝評(píng)論區(qū) lanus 大佬(不知道為什么@不到)的啟發(fā),補(bǔ)充兩點(diǎn)。
總結(jié)
以上是生活随笔為你收集整理的AD19 add pins to nets错误_为什么我认为Rust的Result错误处理方式不如Exception的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 隐藏域input里面放当前时间_【小A问
- 下一篇: 陶晶驰stm32_陶晶驰串口屏学习日记(