上一篇文章
它看起來非常復雜,這就是為什么它貼合的塑料蓋上用大號友好字母印上“不要恐慌”的原因之一。——道格拉斯·亞當斯
此項目的標題將更準確地描述為更喜歡返回Result
而不是使用panic!
(但不要驚慌更吸引人)。
Rust的panic機制主要是為程序中不可恢復的錯誤而設計的,默認情況下它會終止發出panic的線程。然而,這個默認值還有其他替代方案。
特別是,來自具有異常系統(如Java或C++)的語言的Rust新手有時會撲向std::panic::catch_unwind作為模擬異常的一種方式,因為它似乎提供了一種在呼叫堆棧更遠的點捕獲恐慌的機制。
fn divide(a: i64, b: i64) -> i64 {if b == 0 {panic!("Cowardly refusing to divide by zero!");}a / b
}
嘗試用無效輸入調用此內容,按預期失敗:
// Attempt to discover what 0/0 is...
let result = divide(0, 0);
thread 'main' panicked at 'Cowardly refusing to divide by zero!', main.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
?
使用catch_unwind
捕捉恐慌的包裝器:
fn divide_recover(a: i64, b: i64, default: i64) -> i64 {let result = std::panic::catch_unwind(|| divide(a, b));match result {Ok(x) => x,Err(_) => default,}
}
似乎工作并模擬catch
:
let result = divide_recover(0, 0, 42);
println!("result = {result}");
result = 42
然而,外表可能是欺騙性的。這種方法的第一個問題是,恐慌并不總是解除;有一個編譯器選項(也可以通過Cargo.toml配置文件設置訪問),可以轉移恐慌行為,以便立即中止該過程:
thread 'main' panicked at 'Cowardly refusing to divide by zero!', main.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
/bin/sh: line 1: 29100 Abort trap: 6 ?cargo run --release
這使得任何模擬異常的嘗試完全受制于更廣泛的項目設置。同樣,一些目標平臺(例如WebAssembly)總是在恐慌時中止,無論任何編譯器或項目設置如何。
恐慌處理暴露的一個更微妙的問題是異常安全:如果恐慌發生在數據結構操作的中途,它會消除數據結構處于自洽狀態的任何保證。眾所周知,自20世紀90年代以來,在存在異常的情況下保留內部不變量是非常困難的;1這是谷歌(著名的)禁止在其C++代碼中使用異常的主要原因之一。
最后,恐慌傳播也與FFI(外部函數接口)邊界的相互作用很差;使用catch_unwind
防止Rust代碼中的恐慌跨越FFI邊界傳播到非Rust調用代碼。
那么,除了恐慌,還有什么辦法呢?處理錯誤條件?對于庫代碼,最好的替代方法是通過返回帶有適當錯誤類型(Item 4)的Result,使錯誤成為其他人的問題。這允許庫用戶自己決定下一步要做什么——這可能涉及將問題傳遞給行中的下一個調用者,通過?操作符。
責任總有止點,一個有用的經驗法則是,恐慌是可以的!(或者unwrap(), expect()等),如果你控制了main;在這一點上,沒有進一步的調用者可以將責任傳遞給。
又一個明智的恐慌!即使在庫代碼中,在很少遇到錯誤的情況下也是如此,并且您不希望用戶不得不使用.unwrap()調用來丟棄他們的代碼。
如果錯誤情況只是因為(例如)內部數據損壞,而不是由于無效的輸入,那么就會引發panic!
是合法的。
允許由無效輸入觸發的恐慌,但此類無效輸入不同尋常,這甚至偶爾也是有用的。當相關切入點成對出現時,這效果最佳:
- 一個“無懈可犯”的版本,其簽名意味著它總是成功的(如果它不能成功,就會驚慌失措)
- 一個“易失”版本,返回一個
Result
對于前者,Rust的API指南表明panic!
應在內聯文檔的特定部分中記錄。
標準庫中的String::from_utf8_unchecked和String::from_utf8入口點是后者的一個例子(盡管在這種情況下,恐慌實際上被推遲到使用由無效輸入構造的aString)。
假設您試圖遵守本項目中的建議,有幾件事需要記住。首先,恐慌可以以不同的形式出現;避免panic!
還涉及避免以下內容:
更難發現的是以下東西:
slice[index]
當指數不在范圍之內時x / y
當y
為零時
關于避免恐慌的第二個觀察是,一個涉及人類持續警惕的計劃從來都不是一個好主意。
然而,對機器的持續警惕是另一回事:在您的持續集成系統中添加檢查,以發現新的、可能恐慌的代碼要可靠得多。一個簡單的版本可能是最常見的恐慌進入點的簡單grep(如前所示);更徹底的檢查可能涉及來自Rust生態系統的額外工具,例如設置一個構建變體來拉入no_panic板條箱。
1 Tom Cargill1994年在C++報告中的文章探討了C++模板代碼的異常安全有多困難,Herb Sutter的本周大師#8專欄也是如此。
下一篇文章-避免使用反射https://blog.csdn.net/qq_34841911/article/details/139180459