Expand description
A crate for high level error propogation with software controlled backtraces
that are entirely independent of the RUST_BACKTRACE
system.
In Rust development, major crates will often have their own error enums that
work well in their own specialized domain, but when orchestrating many
domains together we run into issues. map_err
is very annoying to work
with. In async
call stacks or cases where we can’t easily control
RUST_BACKTRACE
, we run into an especially annoying problem
where the same kind of error can be returned from multiple places, and we
are sometimes forced into println
debugging to find out where it is
actually from. This crate introduces the StackableErr
trait and a
“stackable” error type that allows for both software-defined error
backtraces and easily converting errors into the stackable error type.
This crate is similar to eyre
, but has a more efficient internal layout
with a ThinVec
array of SmallBox
es, works with no_std
, implements
core::error::Error
, and more.
Some partial examples of what using the crate looks like:
f.map_err(|e| Error::from_err(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")?;
// arbitrary things implementing `Display + Send + Sync + 'static` can be stacked
f.stack_err(arbitrary)?;
// readily swappable with `anyhow` and `eyre` due to extra method, trait, and
// struct aliases
f.wrap_err(arbitrary)?;
f.context(arbitrary)?;
// The trait is implemented for options so that we don't need `OptionExt` like
// `eyre` does
option.take()
.stack_err("`Struct` has already been taken")?
.wait_with_output()
.await
.stack_err_with(|| {
format!("{self:?}.xyz() -> failed when waiting")
})?;
return Err(Error::from_err(format!(
"failure of {x:?} to complete"
)))
// replace the above with
bail!("failure of {x:?} to complete")
// 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_err(format!("myfunction(.., host: {host})"))),
}
use stacked_errors::{bail, 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" {
bail!("bottom level `StrErr`")
}
if s == "parse invalid" {
// 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_with(|| 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_with(|| 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.
// note that debug mode (used when returning errors from the main function)
// includes terminal styling
println!("{:?}", outer("return error"));
let res = format!("{}", outer("return error").unwrap_err());
assert_eq!(
res,
r#"
at src/lib.rs 45:22
error from innermost("return error") at src/lib.rs 38:10
bottom level `StrErr` at src/lib.rs 12:9"#
);
println!("{:?}", outer("parse invalid"));
let res = format!("{}", outer("parse invalid").unwrap_err());
assert_eq!(
res,
r#"
at src/lib.rs 45:22
error from innermost("parse invalid") at src/lib.rs 38:10
parsing error with "parse invalid" at src/lib.rs 24:14
1:1: Expected unit"#
);
// 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_with(|| {
format!(
"wait_for_ok(num_retries: {num_retries}, delay: {delay:?}) timeout, \
last error stack was:"
)
})
Macros§
- anyhow
- For ease of translating from the
anyhow
crate - bail
- Equivalent to
return Err(Error::from_err(format_args!(...)))
if a string literal,return Err(Error::from_err(expr))
if a single expression, orreturn Err(Error::from_err(format!(...)))
otherwise. - bail_
locationless - The
bail
macro but with_locationless
variations - ensure
- Asserts that a boolean expression is
true
at runtime, returning a stackable error otherwise. - ensure_
eq - Asserts that two expressions are equal to each other (with PartialEq), returning a stackable error if they are equal. Debug is also required if there is no custom message.
- ensure_
ne - Asserts that two expressions are not equal to each other (with PartialEq), returning a stackable error if they are equal. Debug is also required if there is no custom message.
- eyre
- For ease of translating from the
eyre
crate, but also the recommended macro to use if you use this kind of macro - stacked_
get - Applies
get
andstack_err_with(...)?
in a chain, this is compatible with many things. - stacked_
get_ mut - Applies
get_mut
andstack_err_with(...)?
in a chain, this is compatible with many things.
Structs§
- Display
Str - For implementing
Debug
, this wrapper makes strings use theirDisplay
impl rather thanDebug
impl - Probably
NotRoot Cause Error - Used to signal to crates like
super_orchestrator
that an error was probably not the root cause - Stacked
Error - An error struct intended for high level error propogation with programmable backtraces
- Timeout
Error - Used to signal timeouts
- Unit
Error - Used internally when an error needs to be pushed but only the location is important
Traits§
- Stackable
Err - Conversion to and addition to the stack of a stackable_error::Error.
- Stackable
Error Trait - Trait implemented for all
T: Display + Send + Sync + 'static
- Stacked
Error Downcast
Functions§
- shorten_
location - Intended for shortening the file field of
Location
s.
Type Aliases§
- Error
- Result
- A shorthand for core::result::Result<T, stacked_errors::Error>