Crate stacked_errors

source ·
Expand description
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 } }),
] })"#
);

Some other partial examples of what using the crate properly looks like:

f.map_err(|e| Error::from_box(Box::new(e)))?;
// replace the above with
f.stack()?;
f.stack_err(|| ())?;
// replace the above with
f.stack()?;
// if needing to push another arbitrary error onto the stack
f.stack_err(|| ErrorKind::from_err(arbitrary))?;
let dir = self
    .path
    .parent()
    .stack_err(|| "FileOptions::preacquire() -> empty path")?
    .to_str()
    .stack_err(|| "bad OsStr conversion")?;
option.take()
    .stack_err(|| "`Struct` has already had some termination method called")?
    .wait_with_output()
    .await
    .stack_err(|| {
        format!("{self:?}.outer_wait_with_output() -> 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 `crate::Error` you can do this if it is
// preferable over `map`
return match ... {
    Ok(ok) => {
        ...
    }
    Err(e) => Err(e.add_kind(format!("wait_for_ok_lookup_host(.., host: {host})"))),
}
// 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:"
    )
})

Structs

  • This is a wrapper around a Location that shortens the Debug of the file field
  • For implementing Debug, this wrapper makes strings use their Display impl rather than Debug impl
  • An error struct that has an internal stack for different kinds of errors and Locations. This is a replacement for the bad information you get from backtraces within async tasks.
  • Due to trait conflicts and not wanting users to accidentally embed crate::Error in a BoxedErr of another crate::Error, crate::Error itself does not actually implement std::error::Error. This does not pose a problem in most cases, since main functions can return Return<T, Error>. However, if a user absolutely needs an end result struct implementing std::error::Error, they can use this wrapper.

Enums

  • This includes BoxedError for general errors, tag errors, and only standard library errors are directly coded.

Traits

  • Conversion to and addition to the stack of an Error.

Type Definitions