Rust 错误处理中多种类型Error传播的处理方式

有两种方式来聚合多种类型的Error来进行函数间的传播:一种是使用Box<dyn std::error::Error>来用trait object来代表所有类型的错误;另一种是使用自定义的enum来装所有的错误。

主要参考:细说Rust错误处理Result 与可恢复的错误.

TL; DR

  • Box<dyn std::error::Error> 通常用在不对错误进行恢复的时候。(不容易检测到错误类型)
  • 自定义一个enum通常用在需要对错误进行恢复的时候。(容易检测类型)

Box<dyn Error> 方式来聚合Error

fn do_something() -> std::result::Result<(),Box<dyn std::error::Error>>{
    let path = "./dat";
    let v = std::fs::read_to_string(path)?;
    let x = std::str::from_utf8(v.as_bytes())?;
    let u = x.parse::<u32>()?;
    println!("num:{:?}",u);
    Ok(())
}

三者分别返回不同的错误类型,但是都可以用Box<dyn std::error::Error>来装。

  • 优点: 方便书写。
  • 缺点: 在传递后该Result后,该trait object 对应的实际的错误类型难以确定,应该需要用“反射”( Any trait)才能够确定。而不能直接match。

利用 enum 来聚合Error

内容来自细说Rust错误处理

自定义一个error需要实现如下几步:

  • 手动实现impl std::fmt::Display的trait,并实现 fmt(...)方法。
  • 手动实现impl std::fmt::Debugtrait,一般直接添加注解即可:#[derive(Debug)]
  • 手动实现impl std::error::Errortrait,并根据自身error级别是否覆盖std::error::Error中的source()方法。

下面的内容中:

  • CustomError为我们实现的自定义Error
  • CustomError有三个子类型Error
  • CustomError分别实现了三个子类型Error From的trait,将其类型包装为自定义Error的子类型

好了,有了自定义的CustomError,那怎么使用呢? 我们看代码:

use std::io::Error as IoError;
use std::str::Utf8Error;
use std::num::ParseIntError;
use std::fmt::{Display, Formatter};


fn main() -> std::result::Result<(),CustomError>{
    let path = "./dat";
    let v = read_file(path)?;
    let x = to_utf8(v.as_bytes())?;
    let u = to_u32(x)?;
    println!("num:{:?}",u);
    Ok(())
}

///读取文件内容
fn read_file(path: &str) -> std::result::Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

/// 转换为utf8内容
fn to_utf8(v: &[u8]) -> std::result::Result<&str, std::str::Utf8Error> {
    std::str::from_utf8(v)
}

/// 转化为u32数字
fn to_u32(v: &str) -> std::result::Result<u32, std::num::ParseIntError> {
    v.parse::<u32>()
}


#[derive(Debug)]
enum CustomError {
    ParseIntError(std::num::ParseIntError),
    Utf8Error(std::str::Utf8Error),
    IoError(std::io::Error),
}
impl std::error::Error for CustomError{
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self {
            CustomError::IoError(ref e) => Some(e),
            CustomError::Utf8Error(ref e) => Some(e),
            CustomError::ParseIntError(ref e) => Some(e),
        }
    }
}

impl Display for CustomError{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self {
            CustomError::IoError(ref e) => e.fmt(f),
            CustomError::Utf8Error(ref e) => e.fmt(f),
            CustomError::ParseIntError(ref e) => e.fmt(f),
        }
    }
}

impl From<ParseIntError> for CustomError {
    fn from(s: std::num::ParseIntError) -> Self {
        CustomError::ParseIntError(s)
    }
}

impl From<IoError> for CustomError {
    fn from(s: std::io::Error) -> Self {
        CustomError::IoError(s)
    }
}

impl From<Utf8Error> for CustomError {
    fn from(s: std::str::Utf8Error) -> Self {
        CustomError::Utf8Error(s)
    }
}