7 个版本 (4 个重大更新)

0.5.2 2024年4月18日
0.5.1 2024年4月5日
0.5.0 2023年11月7日
0.4.0 2023年8月1日
0.1.0 2023年5月29日

#585数据结构

Download history 56/week @ 2024-05-03 18/week @ 2024-05-10 30/week @ 2024-05-17 85/week @ 2024-05-24 37/week @ 2024-05-31 28/week @ 2024-06-07 74/week @ 2024-06-14 32/week @ 2024-06-21 7/week @ 2024-06-28 22/week @ 2024-07-05 70/week @ 2024-07-12 99/week @ 2024-07-19 106/week @ 2024-07-26 46/week @ 2024-08-02 26/week @ 2024-08-09 132/week @ 2024-08-16

每月 347 次下载
super_orchestrator 中使用

MIT/Apache

40KB
485 代码行

Stacked Errors

一个用于带有程序化回溯的高级错误传播的 crate。

在 Rust 开发中,主要的 crate 通常会有它们自己的错误枚举,这些枚举在其特定的领域内表现良好,但当我们将许多领域组合在一起时,会遇到问题。使用 map_err 非常麻烦。在 async 调用堆栈中,我们遇到了一个特别麻烦的问题,即相同的错误可以从多个地方返回,有时我们被迫使用 println 调试以确定它的实际来源。此 crate 引入了 StackableErr trait 和一种 "可堆叠" 的错误类型,允许进行软件定义的错误回溯和转换。

使用该 crate 的部分示例

f.map_err(|e| Error::from_box(Box::new(e)))?;
// replace the above with
f.stack()?; // uses `#[track_caller]` when an error is being propagated
let dir = self
    .path
    .parent()
    .stack_err(|| "FileOptions::preacquire() -> empty path")?
    .to_str()
    .stack_err(|| "bad OsStr conversion")?;
// if needing to push another arbitrary error onto the stack
f.stack_err(|| ErrorKind::from_err(arbitrary))?;
option.take()
    .stack_err(|| "`Struct` has already been taken")?
    .wait_with_output()
    .await
    .stack_err(|| {
        format!("{self:?}.xyz() -> failed when waiting")
    })?;
// strings and some std errors can be created like this,
return Err(Error::from(format!(
    "failure of {x:?} to complete"
)))
// otherwise use this (also note that `Error::from*` includes
// `#[track_caller]` location, no need to add on a `stack` call)
return Err(Error::from_err(needs_boxing))
// when the error type is already `stacked_errors::Error` you can do this if it is
// preferable over `map`
return match ... {
    Ok(ok) => {
        ...
    }
    Err(e) => Err(e.add_kind(format!("myfunction(.., host: {host})"))),
}
use stacked_errors::{Error, Result, StackableErr};

// Note that `Error` uses `ThinVec` internally, which means that it often
// takes up only the stack space of a `usize` or the size of the `T` plus
// a byte.
fn innermost(s: &str) -> Result<u8> {
    if s == "return error" {
        // When creating the initial `Result<_, Error>` from something that
        // is directly representable in a `ErrorKind` (i.e. not needing
        // `BoxedErr`), use this `Err(Error::from(...))` format. This
        // format is cumbersome relative to the other features of this
        // crate, but it is the best solution because of technicalities
        // related to trait collisions at the design level, `Result` type
        // inference with the return type, wanting to keep the directly
        // representable strings outside of a box for performance, and
        // because of the `Display` impl which special cases them.

        return Err(Error::from("bottom level `StrErr`"))
    }
    if s == "parse invalid" {
        // However, this is the common case where we have some external
        // crate function that returns a `Result<..., E: Error>`. We
        // usually call `StackableErr::stack_err` if we want to attach
        // some message to it right away (it is called with a closure
        // so that it doesn't have impact on the `Ok` cases). Otherwise, we
        // just call `StackableErr::stack` so that just the location is
        // pushed on the stack. We can then use `?` directly.

        let _ = ron::from_str("invalid").stack_err(|| format!("parsing error with \"{s}\""))?;
    }
    Ok(42)
}

fn inner(s: &str) -> Result<u16> {
    // Chainable with other combinators. Use `stack_err` with a message for
    // propogating up the stack when the error is something that should
    // have some mid layer information attached for it for quick diagnosis
    // by the user. Otherwise use just `stack` which will also do error
    // conversion if necessary, avoiding needing to wrangle with `map_err`.

    let x = innermost(s)
        .map(|x| u16::from(x))
        .stack_err(|| format!("error from innermost(\"{s}\")"))?;
    Ok(x)
}

fn outer(s: &str) -> Result<u64> {
    // ...

    let x = inner(s).stack()?;

    // ...
    Ok(u64::from(x))
}

let res = format!("{:?}", outer("valid"));
assert_eq!(res, "Ok(42)");

// The line numbers are slightly off because this is a doc test.
// In order from outer to the innermost call, it lists the location of the
// `stack` call from `outer`, the location of `stack_err` from `inner`,
// the associated error message, the location of either the `Error::from`
// or `stack_err` from `innermost`, and finally the root error message.

let res = format!("{:?}", outer("return error"));
assert_eq!(
    res,
    r#"Err(Error { stack: [
Location { file: "src/lib.rs", line: 54, col: 22 },
Location { file: "src/lib.rs", line: 47, col: 10 },
error from innermost("return error")
Location { file: "src/lib.rs", line: 22, col: 20 },
bottom level `StrErr`
] })"#
);

let res = format!("{:?}", outer("parse invalid"));
assert_eq!(
    res,
    r#"Err(Error { stack: [
Location { file: "src/lib.rs", line: 54, col: 22 },
Location { file: "src/lib.rs", line: 47, col: 10 },
error from innermost("parse invalid")
parsing error with "parse invalid"
Location { file: "src/lib.rs", line: 33, col: 42 },
BoxedError(SpannedError { code: ExpectedUnit, position: Position { line: 1, col: 1 } }),
] })"#
);

请注意,.stack_err() 等价于 .stack()

// in commonly used functions you may want `_locationless` to avoid adding
// on unnecessary information if the location is already being added on
return Err(e.add_err_locationless(ErrorKind::TimeoutError)).stack_err(|| {
    format!(
        "wait_for_ok(num_retries: {num_retries}, delay: {delay:?}) timeout, \
         last error stack was:"
    )
})

依赖关系

~0.4–0.9MB
~19K SLoC