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 在 数据结构
每月 347 次下载
在 super_orchestrator 中使用
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