Rust中的錯誤處理
Result枚舉
Rust 中沒有提供類似于 Java、C++ 中的 Exception 機制,而是使用Result
枚舉的方式來實現(xiàn):
pub enum Result
在使用時:
-
如果無錯誤則使用
Ok(T)
返回; -
如果存在錯誤,則使用
Err(E)
包裝錯誤類型返回;
例如:
examples/0_result.rs
#[derive(Debug)] pub enum MyError { Internal(String), InvalidId(String), } fn add(num: i64) -> Result<i64, MyError> { if num < 0 { Err(MyError::InvalidId(String::from("Invalid num!"))) } else { Ok(num + 100000) } } fn main() -> Result<(), MyError> { // fetch_id(-1)?; let res = add(1)?; println!("{}", res); Ok(()) }
上面的代碼首先通過 MyError 枚舉定義了多個可能會出現(xiàn)的錯誤;
隨后,在add
函數(shù)中:
-
當 num 小于 0 時返回錯誤;
-
否則給 num 增加 100000 并返回;
在上面的let res = add(1)?;
中使用了?
操作符,他相當于是一個語法糖:
-
如果被調函數(shù)正常返回則調用
unwrap
獲取其值; -
反之,則將被調函數(shù)的錯誤直接向上返回(相當于直接 return Err);
即上面的語法糖相當于:
let res = match add() { Ok(id) => id, Err(err) => { return Err(err); } };
錯誤類型轉換
上面簡單展示了 Rust 中錯誤的使用;
由于 Rust 是強類型的語言,因此如果在一個函數(shù)中使用?
返回了多個錯誤,并且他們的類型是不同的,還需要對返回的錯誤類型進行轉換,轉為相同的類型!
例如下面的例子:
#[derive(Debug)] pub enum MyError { ReadError(String), ParseError(String), } fn read_file() -> Result<i64, MyError> { // Error: Could not get compiled! let content = fs::read_to_string("/tmp/id")?; let id = content.parse::<i64>()?; } fn main() -> Result<(), MyError> { let id = read_file()?; println!("id: {}", id); Ok(()) }
上面的例子無法編譯通過,原因在于:read_to_string
和parse
返回的是不同類型的錯誤!
因此,如果要能返回,我們需要對每一個錯誤進行轉換,轉為我們所定義的 Error 類型;
例如:
examples/1_error_convert.rs
fn read_file() -> Result<i64, MyError> { // Error: Could not get compiled! // let content = fs::read_to_string("/tmp/id")?; // let id = content.parse::
上面展示了兩種不同的轉換 Error 的方法:
方法一通過 match 匹配手動的對read_to_string
函數(shù)的返回值進行處理,如果發(fā)生了 Error,則將錯誤轉為我們指定類型的錯誤;
方法二通過map_err
的方式,如果返回的是錯誤,則將其轉為我們指定的類型,這時就可以使用?
返回了;
相比之下,使用 map_err 的方式,代碼會清爽很多!
From Trait
上面處理錯誤的方法,每次都要對錯誤的類型進行轉換,比較麻煩;
Rust 中提供了 From Trait,在進行類型匹配時,如果提供了從一個類型轉換為另一個類型的方法(實現(xiàn)了某個類型的 From Trait),則在編譯階段,編譯器會調用響應的函數(shù),直接將其轉為相應的類型!
例如:
examples/2_from_trait.rs
#[derive(Debug)] pub enum MyError { ReadError(String), ParseError(String), } impl From
在上面的代碼中,我們?yōu)?MyError 類型的錯誤分別實現(xiàn)了轉換為std::Error
和std::ParseIntError
類型的 From Trait;
因此,在 read_file 函數(shù)中就可以直接使用?
向上返回錯誤了!
但是上面的方法需要為每個錯誤實現(xiàn) From Trait 還是有些麻煩,因此出現(xiàn)了thiserror以及anyhow庫來解決這些問題;
其他第三方庫
thiserror
上面提到了我們可以為每個錯誤實現(xiàn) From Trait 來直接轉換錯誤類型,thiserror
庫就是使用這個邏輯;
我們可以使用 thiserror 庫提供的宏來幫助我們生成到對應類型的 Trait;
例如:
examples/3_thiserror.rs
#[derive(thiserror::Error, Debug)] pub enum MyError { #[error("io error.")] IoError(#[from] std::Error), #[error("parse error.")] ParseError(#[from] std::ParseIntError), } fn read_file() -> Result<i64, MyError> { // Could get compiled! let content = fs::read_to_string("/tmp/id")?; let id = content.parse::<i64>()?; Ok(id) } fn main() -> Result<(), MyError> { let id = read_file()?; println!("id: {}", id); Ok(()) }
我們只需要對我們定義的類型進行宏標注,在編譯時這些宏會自動展開并實現(xiàn)對應的 Trait;
展開后的代碼如下:
#![feature(prelude_import)] #[prelude_import] use std::*; #[macro_use] extern crate std; use std::fs; pub enum MyError { #[error("io error.")] IoError(#[from] std::Error), #[error("parse error.")] ParseError(#[from] std::ParseIntError), } #[allow(unused_qualifications)] impl std::Error for MyError { fn source(&self) -> std::Option<&(dyn std::Error + 'static)> { use thiserror::AsDynError; #[allow(deprecated)] match self { MyError::IoError { 0: source, .. } => std::Option::Some(source.as_dyn_error()), MyError::ParseError { 0: source, .. } => { std::Option::Some(source.as_dyn_error()) } } } } #[allow(unused_qualifications)] impl std::Display for MyError { fn fmt(&self, __formatter: &mut std::Formatter) -> std::Result { #[allow(unused_variables, deprecated, clippy::used_underscore_binding)] match self { MyError::IoError(_0) => { let result = __formatter.write_fmt(::new_v1(&["io error."], &[])); result } MyError::ParseError(_0) => { let result = __formatter.write_fmt(::new_v1(&["parse error."], &[])); result } } } } #[allow(unused_qualifications)] impl std::From
可以看到實際上就是為 MyError 實現(xiàn)了對應錯誤類型的 From Trait;
thiserror 庫的這種實現(xiàn)方式,還需要為類型指定要轉換的錯誤類型;
而下面看到的 anyhow 庫,可以將錯誤類型統(tǒng)一為同一種形式;
anyhow
如果你對 Go 中的錯誤類型不陌生,那么你就可以直接上手 anyhow 了!
來看下面的例子:
examples/4_anyhow.rs
use anyhow::Result; use std::fs; fn read_file() -> Result<i64> { // Could get compiled! let content = fs::read_to_string("/tmp/id")?; let id = content.parse::<i64>()?; Ok(id) } fn main() -> Result<()> { let id = read_file()?; println!("id: {}", id); Ok(()) }
注意到,上面的 Result 類型為anyhow::Result
,而非標準庫中的 Result 類型!
anyhow
為Result
實現(xiàn)了Context
Trait:
impl
在Context
中提供了context
函數(shù),并且將原來的Result
轉成了Result
;
因此,最終將錯誤類型統(tǒng)一為了anyhow::Error
類型;
附錄
源代碼:
-
https://github.com/JasonkayZK/rust-learn/tree/error
審核編輯:湯梓紅
-
JAVA
+關注
關注
19文章
2952瀏覽量
104484 -
函數(shù)
+關注
關注
3文章
4277瀏覽量
62325 -
C++
+關注
關注
21文章
2100瀏覽量
73453 -
枚舉
+關注
關注
0文章
16瀏覽量
4567 -
Rust
+關注
關注
1文章
228瀏覽量
6542
原文標題:[Rust筆記] Rust中的錯誤處理
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論