概述
Rust和Go对于错误处理的选择上比较一致,选择了Error而不是Exception的方案,对于finally的需求,Go通过defer机制保证资源的最终释放,Rust因为自动释放内存的机制,因此使用Guard模式即可保证资源的释放。Rust更强大的是,为Error制定了标准Result,并且得益于Rust强大enum模式,让Error变得非常清晰。
details
basic
sdt::error::Error
- sdt::error::Error 所有方法都有默认实现
- backtrace和Go的errors.Is比较类似,都是用于追溯err的上游类型,性能也都比较差
//Error tarit包含多个方法,但通常情况下impl的时候除了source方法其他无需重写
pub trait Error: Debug + Display {
//如果该错误类型中包含了底层的错误Err,那么source方法应该返回Some(err),如果没有返回None。不重写则默认为None
fn source(&self) -> Option<&(dyn Error + 'static)>;
//type_id():该方法被隐藏
fn type_id(&self, _: private::Internal) -> TypeId;
//backtrace():返回发生此错误的堆栈追溯,目前为unstable,默认禁用,且占用大量内存,性能很差
fn backtrace(&self) -> Option<&Backtrace>;
//description():已废弃,改使用Display
fn description(&self) -> &str;
//cause():已废弃,改使用source()
fn cause(&self) -> Option<&dyn Error>;
}
方案
自定义错误类型
- 手动实现Error,只需要实现fmt::Display的1个方法
- 手动维护Error类型成本不上不下,如果不是专业的三方库,不利于快速开发
- Error enum的设计对程序员要求也比较高,否则容易设计出奇怪和兼容性差的错误类型
- 实现了From后,可以把一些标准库的错误类型转化为自己的类型,就可以使用?自动转换了
#[derive(Debug)]
pub enum MyError {
BadSchema(String, String, String),
IO(io::Error),
Read,
Receive,
Send,
}
impl Error for MyError {}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
}
}
}
丢弃错误类型 Result<_, Box<dyn Error>>
- 因为Rust本身为Box实现了From方法,所以?可以把任何Error类型转化为Box
fn test_error() -> Result<i32, Box<dyn Error>> {
let s = std::fs::read_to_string("test123.txt")?;
let n = s.trim().parse::<i32>()?;
Ok(n)
}
match test_error() {
Ok(_) => {}
Err(e) => {
println!("err {}", e)
}
}
anyhow 超轻错误封装
- anyhow::Error类似于Box,但他有一个word存储优势,具备Send, Sync 和 'static特性,并支持backtrace
- anyhow::Result是std的Result类型别名,只有一个泛型参数,因为E已经是anyhow了
- context方法可以为error添加上额外信息
use anyhow::{anyhow, Context, Result};
fn pause(&mut self) -> Result<()> {
return Err(anyhow!("err: {:?}", status));
}
anyhow!(e).context("context message")
thiserror 注解自动生成代码
- 特性太多,非大型模块慎用