Rust 错误处理入门

125 阅读2分钟

概述

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 注解自动生成代码

  • 特性太多,非大型模块慎用

ref

关于 Rust 错误处理的思考

anyhow