Skip to content

gmcgoldr/stackerror

main BranchesTags

Go to file Code

Folders and files

Name| Name| Last commit message| Last commit date
---|---|---|---

Latest commit

History

27 Commits impl| impl
src| src
.gitignore| .gitignore
Cargo.lock| Cargo.lock
Cargo.toml| Cargo.toml
LICENSE| LICENSE
README.md| README.md
View all files

Repository files navigation

Stack Error

Stack Error 降低了为你的项目设计错误处理方案的前期成本,让你专注于编写优秀的库和应用程序。 Stack Error 有三个目标:

  1. 提供类似于 anyhow 的人体工学设计。
  2. 创建信息丰富的错误消息,方便调试。
  3. 提供类型化的数据,方便运行时错误处理。

概述

use stackerror::prelude::*;
pub fn process_data(data: &str) -> StackResult<String> {
  let data: Vec<String> = serde_json::from_str(data)
    .map_err(stack_map!(StackError, "data is not a list of strings"))?;
  data.first()
    .cloned()
    .ok_or_else(stack_else!(StackError, "data is empty"))
}

在这个例子中,[stack_map!] 和 [stack_err!] 构建了 [StackError] 的新实例,将文件名和行号信息添加到消息中。在 [stack_err!] 的情况下,错误消息堆叠在现有错误之上。请注意,宏用于简化常见操作,并且可以使用闭包而不是宏来实现相同的结果。 如果数据不是列表,则生成的错误消息将如下所示:

Error: expected value at line 1 column 1
src/process.rs:4 data is not a list of strings

首先打印 serde 错误,然后是带有文件名和行号的 StackError 消息。

let data = if data.err_code() == Some(&ErrorCode::HttpTooManyRequests) {
  // retry
} else {
  data?
};

[ErrorCode] 包括 HTTP 错误代码、[std::io::ErrorKind] 代码和一些运行时代码,以涵盖非 HTTP 和非 IO 的情况。你可以按照后面的示例所述派生你自己的错误代码。

理由

Rust 错误处理生态系统主要建立在两个库之上: anyhowthiserror。 Stack Error 旨在探索这两个库之间的空间:提供符合人体工学的错误处理和适合库开发的错误类型。

Anyhow 比较

使用 anyhow 可以快速进行开发,因为错误处理几乎总是只是添加 ? 运算符的问题。但这可能会减慢调试过程。考虑以下示例:

use serde::Deserialize;
use anyhow::Result;
#[derive(Debug, Deserialize)]
struct Config {
  key: String,
}
fn print_config(data: &str) -> Result<()> {
  let config: Config = serde_json::from_str(data)?;
  println!("{:?}", config);
  Ok(())
}
fn main() -> Result<()> {
  print_config(r#"{"key": "value", "invalid"}"#)?;
  Ok(())
}

生成的错误消息是:Error: expected : at line 1 column 27。该消息清楚地说明了哪里出错了,但没有说明 如何 出错:程序解码的是什么,以及为什么需要该值。使用 RUST_BACKTRACE=1 运行会打印一个回溯,可以回答这些问题,尽管它包含近 20 个无关的帧。调试此示例是可行的,但有点麻烦。 使用 ? 运算符处理所有错误可以加快编写过程,但会使调试更加困难,从而阻碍整个开发过程。 anyhow 提供了一种替代方案: anyhow::Context trait。 使用 context 方法有助于提供清晰的错误消息,以回答 什么 出错了以及 如何 出错了。它还有助于通过将错误源与其相应的错误消息并置来记录代码。这是 Stack Error 的 stack_err 方法的灵感来源。 随着应用程序的增长,应用程序和库之间的区别可能会变得模糊,因为引入了模块来支持应用程序代码。你最终可能会发现你想要处理一些错误。你可以使用 anyhow::Error::downcast,但这很麻烦,因为你需要尝试向下转换为每种可能的错误类型。

Thiserror 比较

thiserror 库提供了灵活的工具来促进自定义错误类型的创建。没有一种使用它的方法,因此很难进行直接比较,只能说:Stack Error 是主观的,旨在尽可能小,但通常很有用。这减少了为你的项目开发(并坚持)良好的错误处理策略所需的工作量。相比之下,当使用 thiserror 时,你必须在项目开始时就许多关于在哪里以及如何定义错误做出决定。如果你没有过多考虑错误的设计,你最终可能会:

库结构

该库的核心是 [StackError] 结构体和 [ErrorStacks] trait。 [ErrorCode] 枚举可用于将错误代码添加到任何 [ErrorStacks]。并且提供了 [stack_msg!], [stack_err!], [stack_map!] 和 [stack_else!] 宏来简化 [Result] 上的常见操作,并将文件名和行号信息添加到错误消息。 通常,你将使用 [prelude] 模块访问这些内容,该模块还定义了 [StackResult]。

示例

通过使用 [derive_stack_error] 宏创建你的错误类型:

// src/errors.rs
pub use stackerror::prelude::*;
#[derive_stack_error]
struct Error(StackError);
pub type Result<T> = std::result::Result<T, Error>;

你可以从任何 [std::fmt::Display] 的内容构建新错误:

use stackerror::prelude::*;
fn process_data() -> StackResult<()> {
  Err(StackError::new("failed to process data"))
}

你可以使用 [stack_msg!] 宏在错误消息中包含文件和行信息:

use stackerror::prelude::*;
fn process_data() -> StackResult<()> {
  Err(StackError::new(stack_msg!("failed to process data")))
}

你可以包含可选的错误处理信息:

use stackerror::prelude::*;
fn process_data() -> StackResult<()> {
  Err(
    StackError::new(stack_msg!("failed to process data"))
    .with_err_code(ErrorCode::HttpImATeapot)
    .with_err_uri("https://example.invalid/teapot")
  )
}

你可以将错误链接在一起以在错误消息中提供上下文:

use stackerror::prelude::*;
pub read_data() -> StackResult<String> {
  Err(StackError::new(stack_msg!("failed to read data")))
}
pub process_data() -> StackResult<()> {
  // NOTE: stack_err can be called directly on the Result
  read_data().stack_err(stack_msg!("failed to process data"))
}

这将导致类似以下的错误消息:

src/main:8 failed to process data
src/main:4 failed to read data

[stack_err!] 宏为常见模式 Err(Error::new(stack_msg!(...))) 提供了一种简写:

use stackerror::prelude::*;
pub read_data() -> StackResult<String> {
  stack_err!("failed_to_read_data")
}

你可以包装现有错误:

use stackerror::prelude::*;
pub fn process_data(data: &str) -> Vec<String> {
  serde_json::from_str(data)
    .map_err(StackError::new)
    .stack_err(stack_msg!("data is not a list of strings"))
}

[stack_map!] (以及类似的 [stack_else! ])宏为此常见模式提供了一种简写。它们接受要包装原始错误的错误类型,以及要堆叠在其上的错误消息。错误类型必须是 [ErrorStacks]。

use stackerror::prelude::*;
pub fn process_data(data: &str) -> Vec<String> {
  serde_json::from_str(data)
    .map_err(stack_map!(StackError, "data is not a list of strings"))
}

你可以通过在使用 [derive_stack_error] 的范围内定义 [ErrorCode] 类型来使用你自己的错误代码:

use stackerror::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ErrorCode {
  SomethingWentWrong,
  SomethingElseWentWrong,
}
#[derive_stack_error]
struct ErrorWithCustomCodes(StackError);

关于

一个实用的 Rust 错误处理库,它提供了有助于调试的字符串,以及用于运行时错误处理的结构化数据。

资源

Readme

许可证

MIT license Activity

Stars

16 stars

Watchers

1 watching

Forks

1 fork Report repository

Releases

No releases published

Packages 0

No packages published

Contributors 2

Languages

Footer

© 2025 GitHub, Inc.

Footer navigation

You can’t perform that action at this time.