title: "【Rust錯誤處理】使用thiserror
+anyhow
來優(yōu)雅便捷地處理錯誤"
date: 2020-03-08T17:37:00+08:00
description: ""
featured_image: ""
categories: "Rust"
tags: ["錯誤處理"]
錯誤處理琅催,一直是編程語言的一個設(shè)計難點柳譬,各種編程語言的錯誤處理機制都不盡相同完箩,并且各有優(yōu)劣粥诫,Rust也不例外籍滴。
Rust使用Result
這個enum來代表正確或錯誤的情況待逞,思路來源于Haskell
和OCaml
等函數(shù)式語言轩缤,可以說是十分的優(yōu)雅转唉,特別體現(xiàn)在對Option
和Iterator
的交互上皮钠,但是對于新人入門來說需要一定的學(xué)習(xí)成本。
從我自身的使用情況來說赠法,我總結(jié)了一下目前Rust(版本1.41.1)的錯誤處理的幾個缺點:
-
多種類型的
Error
在同一個函數(shù)中需要處理時麦轰,必須要有一個共同的可以Into
的Error
類型作為函數(shù)返回值,比如下面的代碼:fn foo() -> Result<(), FooError> { let one: Result<(), OneError> = fn_one(); one?; let two: Result<(), TwoError> = fn_two(); two?; Ok(()) }
那么
FooError
必須有實現(xiàn)From<OneError>
和From<TwoError>
砖织,慣常的做法是寫一個enum將所有的錯誤都封裝起來款侵,并且實現(xiàn)Display
、Debug
侧纯、Error
還有From<T>
這幾個trait新锈,手寫的話比較枯燥乏味。 Error
trait的backtrace
還未穩(wěn)定眶熬,雖然說backtrace
對性能會有損耗妹笆,但是沒有backtrace
在遇到錯誤拋出的時候,如果錯誤沒有一些明確的信息娜氏,就很難定位到是哪里的代碼拋的這個錯誤拳缠,特別是對于第三方庫拋的錯誤,對于開發(fā)調(diào)試和生產(chǎn)排查問題都造成了不便贸弥,在Fix the Error trait這個RFC里面有提出窟坐。
針對第一點情況,Rust界的大佬dtolnay
設(shè)計了兩個crate:thiserror和anyhow绵疲。在這之前已經(jīng)有很多提升錯誤處理的庫了哲鸳,比如failure
、error-chain
和quickerror
等等盔憨,這些庫我都使用過帕胆,而且這些庫都有一定程度的互相借鑒,但最后最終還是解決thiserror
+anyhow
是最易用和實用的般渡。
注意事項:
thiserror
是給lib使用的懒豹,而anyhow
是給bin程序使用芙盘,當(dāng)然bin程序可以使用thiser
+anyhow
,但是切記anyhow
不要在lib里面使用脸秽。
這兩個crate的使用方法在README中也說得很清楚明白了儒老,所以下面只是簡單得闡述一下:
thiserror
復(fù)制粘貼的thiserror
示例:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
這個其實就是用enum封裝的思路,但是因為是用derive macro
來自動生成的记餐,節(jié)省了很多的時間和敲鍵成本驮樊,也沒有那么無聊。
anyhow
復(fù)制粘貼的anyhow
示例:
use anyhow::Result;
fn get_cluster_info() -> Result<ClusterMap> {
let config = std::fs::read_to_string("cluster.json")?;
let map: ClusterMap = serde_json::from_str(&config)?;
Ok(map)
}
這個就更加簡潔了片酝,直接將anyhow::Result
作為返回值就可以了囚衔,因為它直接實現(xiàn)了From<E> where E: Error
,所以就不需要再維護(hù)一個enum類型了雕沿,但是注意只有在bin程序里面才可以這么搞练湿,因為在lib里面這么做的話,別人在使用的時候想特別處理某種情況的錯誤审轮,只能通過downcast
來處理肥哎,這個設(shè)計是很不友好的。
在bin程序里面如果有一點需要特別處理的錯誤疾渣,比如后端接口中的用戶權(quán)限失敗這種錯誤篡诽,是需要明確標(biāo)記出來(區(qū)分錯誤碼等),讓前端彈出登錄框這種需求的榴捡,這里就可以將thiserror
和anyhow::Result
相結(jié)合使用了:
復(fù)制粘貼的thiserror
示例:
#[derive(Error, Debug)]
pub enum MyError {
...
#[error(transparent)]
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}
對于需要特別對待的錯誤杈女,則手工去用thiserror
維護(hù),對于無關(guān)重要的錯誤吊圾,則統(tǒng)一用Other
去包裝就可以了碧信。