#error #error-management #error-handling #macro #debugging

error_set

一个错误集宏,通过提供定义错误和轻松转换它们的简化方法,简化了错误管理。灵感来自Zig的错误集类型。

11个不稳定版本 (3个破坏性版本)

新功能 0.4.5 2024年8月22日
0.4.4 2024年8月21日
0.4.2 2024年7月24日
0.3.2 2024年4月16日
0.1.0 2024年4月3日

706Rust模式

Download history 17/week @ 2024-05-02 7/week @ 2024-05-09 16/week @ 2024-05-16 9/week @ 2024-05-23 6/week @ 2024-05-30 11/week @ 2024-06-06 12/week @ 2024-06-13 4/week @ 2024-06-20 4/week @ 2024-06-27 9/week @ 2024-07-04 7/week @ 2024-07-11 301/week @ 2024-07-18 111/week @ 2024-07-25 19/week @ 2024-08-01 103/week @ 2024-08-08 71/week @ 2024-08-15

每月400 次下载
用于 surrealdb_migration_engin…

Apache-2.0

46KB
591 代码行

错误集

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(...)]

基本示例
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(_))));
}

典型的项目方法是在一个名为 errors.rs 的文件中有一个单独的 error_set。这使所有错误都在一个地方,并允许您的IDE自动完成 crate::errors:: 以包含所有错误。但是,error_set! 也可以用于快速错误“联合”,不再需要用户手动编写 From<..> 或使用 .map_err(..) 进行这些简单情况。例如。

error_set! {
    FirebaseJwtVerifierCreationError = {
        Reqwest(reqwest::Error),
        Jwt(jsonwebtoken::errors::Error),
    };
}

impl FirebaseJwtVerifier {
    pub async fn new(project_id: String) -> Result<Self, FirebaseJwtVerifierCreationError> {
        let public_keys = Self::fetch_public_keys().await?;
        let decoding_keys: Result<HashMap<String, DecodingKey>, _> = public_keys
            .into_iter()
            .map(|(key, value)| {
                DecodingKey::from_rsa_pem(value.as_bytes()).map(|decoding_key| (key, decoding_key))
            })
            .collect();

        let decoding_keys = decoding_keys?;
        ...
    }
}
Cargo Expand
#[derive(Debug)]
pub enum FirebaseJwtVerifierCreationError {
    Reqwest(reqwest::Error),
    Jwt(jsonwebtoken::errors::Error),
}
#[allow(unused_qualifications)]
impl std::error::Error for FirebaseJwtVerifierCreationError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            FirebaseJwtVerifierCreationError::Reqwest(ref source) => source.source(),
            FirebaseJwtVerifierCreationError::Jwt(ref source) => source.source(),
            #[allow(unreachable_patterns)]
            _ => None,
        }
    }
}
impl core::fmt::Display for FirebaseJwtVerifierCreationError {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let variant_name = match *self {
            FirebaseJwtVerifierCreationError::Reqwest(_) => {
                "FirebaseJwtVerifierCreationError::Reqwest"
            }
            FirebaseJwtVerifierCreationError::Jwt(_) => "FirebaseJwtVerifierCreationError::Jwt",
        };
        f.write_fmt($crate::format_args!("{}", variant_name))
    }
}
impl From<reqwest::Error> for FirebaseJwtVerifierCreationError {
    fn from(error: reqwest::Error) -> Self {
        FirebaseJwtVerifierCreationError::Reqwest(error)
    }
}
impl From<jsonwebtoken::errors::Error> for FirebaseJwtVerifierCreationError {
    fn from(error: jsonwebtoken::errors::Error) -> Self {
        FirebaseJwtVerifierCreationError::Jwt(error)
    }
}

功能标志

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!,可以简洁地处理错误的具体变体,当它们在调用栈中冒泡并传播其余部分时。

tracing / log:
启用对 tracinglog 库的支持。将方法添加到 Result,并在 ResultErr 时执行。它们的工作方式类似于 anyhow.context(..) 方法。例如:

let result: Result<(), &str> = Err("operation failed");

let value: Result<(), &str> = result.warn("This is a warning logged via tracing/log if `Err`");
let value: () = result.error("This is an error logged via tracing/log if `Err`")?;
let value: Result<(), &str> = result.with_trace(|err| format!("Operation failed due to: {}", err));
let value: Option<()> = result.consume_warn();
let value: Option<()> = result.consume_with_error(|err| format!("Operation failed due to: {}", err));
result.swallow_info();
result.swallow_with_debug(|err| format!("Debug info: {:?}", 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,您的项目可以保持清晰精确的错误定义,增强代码的可读性和可维护性,无需手动定义和管理错误关系。

依赖项

~0.3–0.8MB
~19K SLoC