#error #set #enums #err #macro #coerce #io-error

error_set_impl

error_set crate的过程宏实现

9个版本

新版本 0.4.5 2024年8月22日
0.4.4 2024年8月21日
0.4.2 2024年7月24日
0.3.2 2024年4月16日

#8 in #coerce

Download history 14/week @ 2024-05-02 7/week @ 2024-05-09 18/week @ 2024-05-16 14/week @ 2024-05-23 11/week @ 2024-05-30 12/week @ 2024-06-06 15/week @ 2024-06-13 10/week @ 2024-06-20 6/week @ 2024-06-27 10/week @ 2024-07-04 9/week @ 2024-07-11 310/week @ 2024-07-18 113/week @ 2024-07-25 18/week @ 2024-08-01 109/week @ 2024-08-08 74/week @ 2024-08-15

413次每月下载
2个crate中使用(通过error_set

Apache-2.0

41KB
924

错误集合

github crates.io docs.rs build status

错误集合通过提供定义错误和轻松转换它们的简化方法来简化错误管理。因此,错误处理变得既简单又高效。

错误集合受Zig的错误集合的启发,并提供了类似的功能。

而不是定义各种枚举/结构体来表示错误并手动滚动关系,请使用错误集合

use error_set::error_set;

error_set! {
    MediaError = BookParsingError || DownloadError || ParseUploadError;
    BookParsingError = {
        MissingBookDescription,
        IoError(std::io::Error),
    } || BookSectionParsingError;
    BookSectionParsingError = {
        MissingName,
        NoContents,
    };
    DownloadError = {
        InvalidUrl,
        IoError(std::io::Error),
    };
    ParseUploadError = {
        MaximumUploadSizeReached,
        TimedOut,
        AuthenticationFailed,
    };
}
Cargo Expand
#[derive(Debug)]
pub enum MediaError {
    MissingBookDescription,
    IoError(std::io::Error),
    MissingName,
    NoContents,
    InvalidUrl,
    MaximumUploadSizeReached,
    TimedOut,
    AuthenticationFailed,
}
#[allow(unused_qualifications)]
impl std::error::Error for MediaError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            MediaError::IoError(ref source) => source.source(),
            #[allow(unreachable_patterns)]
            _ => None,
        }
    }
}
impl core::fmt::Display for MediaError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            MediaError::MissingBookDescription => "MediaError::MissingBookDescription",
            MediaError::IoError(_) => "MediaError::IoError",
            MediaError::MissingName => "MediaError::MissingName",
            MediaError::NoContents => "MediaError::NoContents",
            MediaError::InvalidUrl => "MediaError::InvalidUrl",
            MediaError::MaximumUploadSizeReached => "MediaError::MaximumUploadSizeReached",
            MediaError::TimedOut => "MediaError::TimedOut",
            MediaError::AuthenticationFailed => "MediaError::AuthenticationFailed",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}
impl From<BookParsingError> for MediaError {
    fn from(error: BookParsingError) -> Self {
        match error {
            BookParsingError::MissingBookDescription => MediaError::MissingBookDescription,
            BookParsingError::IoError(source) => MediaError::IoError(source),
            BookParsingError::MissingName => MediaError::MissingName,
            BookParsingError::NoContents => MediaError::NoContents,
        }
    }
}
impl From<BookSectionParsingError> for MediaError {
    fn from(error: BookSectionParsingError) -> Self {
        match error {
            BookSectionParsingError::MissingName => MediaError::MissingName,
            BookSectionParsingError::NoContents => MediaError::NoContents,
        }
    }
}
impl From<DownloadError> for MediaError {
    fn from(error: DownloadError) -> Self {
        match error {
            DownloadError::InvalidUrl => MediaError::InvalidUrl,
            DownloadError::IoError(source) => MediaError::IoError(source),
        }
    }
}
impl From<ParseUploadError> for MediaError {
    fn from(error: ParseUploadError) -> Self {
        match error {
            ParseUploadError::MaximumUploadSizeReached => MediaError::MaximumUploadSizeReached,
            ParseUploadError::TimedOut => MediaError::TimedOut,
            ParseUploadError::AuthenticationFailed => MediaError::AuthenticationFailed,
        }
    }
}
impl From<std::io::Error> for MediaError {
    fn from(error: std::io::Error) -> Self {
        MediaError::IoError(error)
    }
}
#[derive(Debug)]
pub enum BookParsingError {
    MissingBookDescription,
    IoError(std::io::Error),
    MissingName,
    NoContents,
}
#[allow(unused_qualifications)]
impl std::error::Error for BookParsingError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            BookParsingError::IoError(ref source) => source.source(),
            #[allow(unreachable_patterns)]
            _ => None,
        }
    }
}
impl core::fmt::Display for BookParsingError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            BookParsingError::MissingBookDescription => "BookParsingError::MissingBookDescription",
            BookParsingError::IoError(_) => "BookParsingError::IoError",
            BookParsingError::MissingName => "BookParsingError::MissingName",
            BookParsingError::NoContents => "BookParsingError::NoContents",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}
impl From<BookSectionParsingError> for BookParsingError {
    fn from(error: BookSectionParsingError) -> Self {
        match error {
            BookSectionParsingError::MissingName => BookParsingError::MissingName,
            BookSectionParsingError::NoContents => BookParsingError::NoContents,
        }
    }
}
impl From<std::io::Error> for BookParsingError {
    fn from(error: std::io::Error) -> Self {
        BookParsingError::IoError(error)
    }
}
#[derive(Debug)]
pub enum BookSectionParsingError {
    MissingName,
    NoContents,
}
#[allow(unused_qualifications)]
impl std::error::Error for BookSectionParsingError {}

impl core::fmt::Display for BookSectionParsingError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            BookSectionParsingError::MissingName => "BookSectionParsingError::MissingName",
            BookSectionParsingError::NoContents => "BookSectionParsingError::NoContents",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}
#[derive(Debug)]
pub enum DownloadError {
    InvalidUrl,
    IoError(std::io::Error),
}
#[allow(unused_qualifications)]
impl std::error::Error for DownloadError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            DownloadError::IoError(ref source) => source.source(),
            #[allow(unreachable_patterns)]
            _ => None,
        }
    }
}
impl core::fmt::Display for DownloadError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            DownloadError::InvalidUrl => "DownloadError::InvalidUrl",
            DownloadError::IoError(_) => "DownloadError::IoError",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}
impl From<std::io::Error> for DownloadError {
    fn from(error: std::io::Error) -> Self {
        DownloadError::IoError(error)
    }
}
#[derive(Debug)]
pub enum ParseUploadError {
    MaximumUploadSizeReached,
    TimedOut,
    AuthenticationFailed,
}
#[allow(unused_qualifications)]
impl std::error::Error for ParseUploadError {}

impl core::fmt::Display for ParseUploadError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            ParseUploadError::MaximumUploadSizeReached => {
                "ParseUploadError::MaximumUploadSizeReached"
            }
            ParseUploadError::TimedOut => "ParseUploadError::TimedOut",
            ParseUploadError::AuthenticationFailed => "ParseUploadError::AuthenticationFailed",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}

这相当于编写完整的展开

error_set! {
    MediaError = {
        IoError(std::io::Error),
        MissingBookDescription,
        MissingName,
        NoContents,
        InvalidUrl,
        MaximumUploadSizeReached,
        TimedOut,
        AuthenticationFailed,
    };
    BookParsingError = {
        MissingBookDescription,
        IoError(std::io::Error),
        MissingName,
        NoContents,
    };
    BookSectionParsingError = {
        MissingName,
        NoContents,
    };
    DownloadError = {
        InvalidUrl,
        IoError(std::io::Error),
    };
    ParseUploadError = {
        MaximumUploadSizeReached,
        TimedOut,
        AuthenticationFailed,
    };
}

上述任何子集都可以通过.into()?转换为超集。这使得正确作用域和传递错误变得容易。错误枚举和错误变体还可以接受文档注释和属性,如#[derive(...)]。典型的项目方法是有一个包含单个error_seterrors.rs文件。这使所有错误都在一个地方,并允许您的IDE自动完成crate::errors::的所有错误。

基本示例
use error_set::error_set;

error_set! {
    MediaError = {
        IoError(std::io::Error)
    } || BookParsingError || DownloadError || ParseUploadError;
    BookParsingError = {
        MissingBookDescription,
        CouldNotReadBook(std::io::Error),
    } || BookSectionParsingError;
    BookSectionParsingError = {
        MissingName,
        NoContents,
    };
    DownloadError = {
        InvalidUrl,
        CouldNotSaveBook(std::io::Error),
    };
    ParseUploadError = {
        MaximumUploadSizeReached,
        TimedOut,
        AuthenticationFailed,
    };
}

fn main() {
    let book_section_parsing_error: BookSectionParsingError = BookSectionParsingError::MissingName;
    let book_parsing_error: BookParsingError = book_section_parsing_error.into();
    assert!(matches!(book_parsing_error, BookParsingError::MissingName));
    let media_error: MediaError = book_parsing_error.into();
    assert!(matches!(media_error, MediaError::MissingName));

    let io_error = std::io::Error::new(std::io::ErrorKind::OutOfMemory, "oops out of memory");
    let result_download_error: Result<(), DownloadError> = Err(io_error).coerce(); // `.coerce()` == `.map_err(Into::into)`
    let result_media_error: Result<(), MediaError> = result_download_error.coerce(); // `.coerce()` == `.map_err(Into::into)`
    assert!(matches!(result_media_error, Err(MediaError::IoError(_))));
}

功能标志

coerce_macro: 每个错误集合都会生成一个coerce!宏,以帮助处理部分相交集合之间的转换。

let val = coerce!(setx => {
                    Ok(val) => val,
                    Err(SetX::X) => {}, // handle disjointedness
                    { Err(SetX) => return Err(SetY) } // terminal coercion
                })?;
更多信息

给定

error_set! {
   SetX = {
       X
   } || Common;
   SetY = {
       Y
   } || Common;
   Common = {
       A,
       B,
       C,
       D,
       E,
       F,
       G,
       H,
   };
}

而不是编写

fn setx_result_to_sety_result() -> Result<(), SetY> {
   let _ok = match setx_result() {
       Ok(ok) => ok,
       Err(SetX::X) => {} // handle disjointedness
       Err(SetX::A) => {
           return Err(SetY::A);
       }
       Err(SetX::B) => {
           return Err(SetY::B);
       }
       Err(SetX::C) => {
           return Err(SetY::C);
       }
       Err(SetX::D) => {
           return Err(SetY::D);
       }
       Err(SetX::E) => {
           return Err(SetY::E);
       }
       Err(SetX::F) => {
           return Err(SetY::F);
       }
       Err(SetX::G) => {
           return Err(SetY::G);
       }
       Err(SetX::H) => {
           return Err(SetY::H);
       }
   };
   Ok(())
}

可以写这个,编译成上面的match语句

fn setx_result_to_sety_result() -> Result<(), SetY> {
   let _ok = coerce!(setx_result() => {
       Ok(ok) => ok,
       Err(SetX::X) => {}, // handle disjointedness
       { Err(SetX) => return Err(SetY) } // terminal coercion
   });
   Ok(())
}

coerce! 宏是一个扁平快速(没有 tt muncher 🦫)声明式宏,由 error_set! 宏为集合创建。 coerce! 的行为类似于一个常规的 match 语句,但它允许在集合之间有一个终端转换语句。例如:

{ Err(SetX) => return Err(SetY) }
{ Err(SetX) => Err(SetY) }
{ SetX => return SetY }
{ SetX => SetY }

使用 coerce!,可以简洁地处理错误的具体变体,当它们在调用栈中冒泡并传播时。

跟踪: 启用对跟踪包的支持。向 Result 添加了在 Err 上应用的方法。类似于 anyhow 的 .context(..)

let value = result.warn("This a warning that will be passed to tracing if `Err`")?;

日志: 启用对日志包的支持。向 Result 添加了在 Err 上应用的方法。类似于 anyhow 的 .context(..)

let value = result.warn("This a warning that will be passed to log if `Err`")?;

为什么选择 error_set 而不是 thiserroranyhow

如果你的项目不需要处理特定的错误类型,只需将错误向上传播调用栈,那么 anyhow 可能是你的一个不错的选择。它简单直接,无需定义错误类型。

然而,对于需要精确错误处理和区分的库和一般项目,错误管理通常会变得复杂和难以控制,特别是如果出现“大枚举”。

什么是大枚举?

大枚举,或大错误枚举,是将各种错误类型合并到一个大枚举中的枚举,而代码如果拆分成多个枚举则会更加精确。这些通常是由于重构或开发者选择更不侵入式编程方法而产生的。这种方法可能导致效率低下和混淆,因为它包括在某些范围内不相关的错误变体。

示例场景

考虑以下函数及其相应的错误类型

  • func1 可以产生错误 ab,由 enum1 表示。
  • func2 可以产生错误 cd,由 enum2 表示。
  • func3 调用了 func1func2

如果 func3 没有处理来自 func1func2 的错误,它必须返回一个包含变体 abcd 的错误枚举。如果没有像 error_set 这样的工具,开发者可能会因为复杂性和困难而跳过定义 enum1enum2,而是创建一个包含所有可能错误变体(abcd)的大枚举。这意味着调用 func1func2 的任何调用者都必须处理所有这些情况,即使在这些特定上下文中不可能。

error_set 如何简化错误管理

error_set 允许您快速、精确地定义错误。正确地设置错误范围非常简单,无需包裹各种错误枚举类型,只需使用 .into()?(或 coerce! 宏)。这种方法确保每个函数只处理相关的错误变体,避免了巨大枚举带来的杂乱和低效。通过使用 error_set,您的项目可以保持清晰和精确的错误定义,提高代码的可读性和可维护性,无需手动定义和管理错误关系。

依赖关系

~300–740KB
~18K SLoC