在Rust中使用anyhow可以很方便得去处理错误, anyhow会自动将错误信息封装成anyhow::Error中, 这里记录下为什么使用, 如何使用
Go中如何处理
Go中的错误处理是通过函数多返回值+实现error接口来实现的, 无论具体的错误是什么, 只要实现了error接口, 都可以作为返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"io"
"os"
)
func readFile ( filepath string ) ([] byte , error ) {
file , err := os . Open ( filepath )
if err != nil {
return nil , err
}
return io . ReadAll ( file )
}
Rust中如何处理
不使用anyhow
1
2
3
4
5
6
7
8
9
10
11
12
use std ::fs ;
use std ::io ;
fn read_file ( filepath : & str ) -> io ::Result < String > {
fs ::read_to_string ( filepath )
}
fn main () -> io ::Result < () > {
let content = read_file ( "example.txt" ) ? ;
println! ( "File content: \n {} " , content );
Ok (())
}
上面代码非常简单, 如果文件不存在就会报下面的错误, 提示找不到文件, 但是我们却无法知道具体是哪个文件
1
Error: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }
使用anyhow
代码中的返回值我明确了是anyhow::Result<T>
1
2
3
4
5
6
7
8
9
10
11
12
13
use anyhow ::Context ;
use std ::fs ;
fn read_file ( filepath : & str ) -> anyhow ::Result < String > {
fs ::read_to_string ( filepath )
. with_context ( || format! ( "Failed to read file: {} " , filepath ))
}
fn main () -> anyhow ::Result < () > {
let content = read_file ( "example.txt" ) ? ;
println! ( " {} " , content );
Ok (())
}
使用anyhow之后, 我们可以附加一些信息到错误中(上面代码高亮部分), 此时错误信息如下
1
2
3
4
Error: Failed to read file: example.txt
Caused by:
系统找不到指定的文件。 (os error 2)
使用anyhow进行错误传播
如果不使用anyhow
注意
Rust的标准库中?运算符只能自动传播跟返回类型相同的错误, 例如:
1
2
3
4
fn foo () -> Result < (), std ::io ::Error > {
let _ = "123" . parse ::< u32 > () ? ; // ❌ 这里是 ParseIntError,不兼容
Ok (())
}
如果我们的逻辑中有多个不同类型的错误, 就无法使用?运算符进行错误传播了, 此时可以使用Box<dyn std::error::Error>
1
2
3
4
5
fn parse_config ( path : & str ) -> Result < u16 , Box < dyn std ::error ::Error >> {
let content = std ::fs ::read_to_string ( path ) ? ;
let port : u16 = content . trim (). parse () ? ;
Ok ( port )
}
或者我们也可以使用enum定义我们自己的错误类型, 然后实现std::error::Error trait, 下面代码build没有问题, 但是写起来有点麻烦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
use std ::fs ;
use std ::io ;
use std ::num ::ParseIntError ;
#[derive(Debug)]
enum MyError {
Io ( io ::Error ),
Parse ( ParseIntError ),
}
impl std ::fmt ::Display for MyError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
MyError ::Io ( e ) => write! ( f , "I/O error: {}" , e ),
MyError ::Parse ( e ) => write! ( f , "Parse error: {}" , e ),
}
}
}
impl From < io ::Error > for MyError {
fn from ( err : io ::Error ) -> MyError {
MyError ::Io ( err )
}
}
impl From < ParseIntError > for MyError {
fn from ( err : ParseIntError ) -> MyError {
MyError ::Parse ( err )
}
}
#[allow(unused)]
fn load_port ( path : & str ) -> Result < u16 , MyError > {
let content = fs ::read_to_string ( path ) ? ; // 自动转换成 MyError::Io
let port : u16 = content . trim (). parse () ? ; // 自动转换成 MyError::Parse
Ok ( port )
}
使用anyhow
使用anyhow之后代码会变得非常简洁
1
2
3
4
5
fn load_port ( path : & str ) -> anyhow ::Result < u16 > {
let content = fs ::read_to_string ( path ) ? ;
let port = content . trim (). parse ::< u16 > () ? ;
Ok ( port )
}
我们还可以附加一些信息
1
2
3
4
5
6
7
8
use anyhow ::Context ;
fn load_port ( path : & str ) -> anyhow ::Result < u16 > {
let content = fs ::read_to_string ( path )
. with_context ( || format! ( "failed to read file: {} " , path )) ? ;
let port = content . trim (). parse ::< u16 > ()
. context ( "failed to parse port number" ) ? ;
Ok ( port )
}
使用anyhow!
anyhow!是一个宏, 可以构建一个错误消息, 可以使用{}来格式化错误消息
1
2
3
fn main () -> anyhow ::Result < () > {
Err ( anyhow ::anyhow! ( "Test anyhow! with format args: {}" , "use anyhow!" ))
}
1
Error: Test anyhow! with format args: use anyhow!