stacked_errors/
lib.rs

1//! A crate for high level error propogation with software controlled backtraces
2//! that are entirely independent of the `RUST_BACKTRACE` system.
3//!
4//! In Rust development, major crates will often have their own error enums that
5//! work well in their own specialized domain, but when orchestrating many
6//! domains together we run into issues. `map_err` is very annoying to work
7//! with. In `async` call stacks or cases where we can't easily control
8//! `RUST_BACKTRACE`, we run into an especially annoying problem
9//! where the same kind of error can be returned from multiple places, and we
10//! are sometimes forced into `println` debugging to find out where it is
11//! actually from. This crate introduces the `StackableErr` trait and a
12//! "stackable" error type that allows for both software-defined error
13//! backtraces and easily converting errors into the stackable error type.
14//!
15//! This crate is similar to `eyre`, but has a more efficient internal layout
16//! with a `ThinVec` array of `SmallBox`es, works with `no_std`, implements
17//! `core::error::Error`, and more.
18//!
19//! Some partial examples of what using the crate looks like:
20//!
21//! ```text
22//! f.map_err(|e| Error::from_err(e))?;
23//! // replace the above with
24//! f.stack()?; // uses `#[track_caller]` when an error is being propagated
25//! ```
26//! ```text
27//! let dir = self
28//!     .path
29//!     .parent()
30//!     .stack_err("FileOptions::preacquire() -> empty path")?
31//!     .to_str()
32//!     .stack_err("bad OsStr conversion")?;
33//! ```
34//! ```text
35//! // arbitrary things implementing `Display + Send + Sync + 'static` can be stacked
36//! f.stack_err(arbitrary)?;
37//! // readily swappable with `anyhow` and `eyre` due to extra method, trait, and
38//! // struct aliases
39//! f.wrap_err(arbitrary)?;
40//! f.context(arbitrary)?;
41//! ```
42//! ```text
43//! // The trait is implemented for options so that we don't need `OptionExt` like
44//! // `eyre` does
45//! option.take()
46//!     .stack_err("`Struct` has already been taken")?
47//!     .wait_with_output()
48//!     .await
49//!     .stack_err_with(|| {
50//!         format!("{self:?}.xyz() -> failed when waiting")
51//!     })?;
52//! ```
53//! ```text
54//! return Err(Error::from_err(format!(
55//!     "failure of {x:?} to complete"
56//! )))
57//! // replace the above with
58//! bail!("failure of {x:?} to complete")
59//! ```
60//! ```text
61//! // when the error type is already `stacked_errors::Error` you can do this if it is
62//! // preferable over `map`
63//! return match ... {
64//!     Ok(ok) => {
65//!         ...
66//!     }
67//!     Err(e) => Err(e.add_err(format!("myfunction(.., host: {host})"))),
68//! }
69//! ```
70//!
71//! ```
72//! use stacked_errors::{bail, Error, Result, StackableErr};
73//!
74//! // Note that `Error` uses `ThinVec` internally, which means that it often
75//! // takes up only the stack space of a `usize` or the size of the `T` plus
76//! // a byte.
77//! fn innermost(s: &str) -> Result<u8> {
78//!     if s == "return error" {
79//!         bail!("bottom level `StrErr`")
80//!     }
81//!     if s == "parse invalid" {
82//!         // This is the common case where we have some external
83//!         // crate function that returns a `Result<..., E: Error>`. We
84//!         // usually call `StackableErr::stack_err` if we want to attach
85//!         // some message to it right away (it is called with a closure
86//!         // so that it doesn't have impact on the `Ok` cases). Otherwise, we
87//!         // just call `StackableErr::stack` so that just the location is
88//!         // pushed on the stack. We can then use `?` directly.
89//!
90//!         let _: () = ron::from_str("invalid")
91//!             .stack_err_with(|| format!("parsing error with \"{s}\""))?;
92//!     }
93//!     Ok(42)
94//! }
95//!
96//! fn inner(s: &str) -> Result<u16> {
97//!     // Chainable with other combinators. Use `stack_err` with a message for
98//!     // propogating up the stack when the error is something that should
99//!     // have some mid layer information attached for it for quick diagnosis
100//!     // by the user. Otherwise use just `stack` which will also do error
101//!     // conversion if necessary, avoiding needing to wrangle with `map_err`.
102//!
103//!     let x = innermost(s)
104//!         .map(|x| u16::from(x))
105//!         .stack_err_with(|| format!("error from innermost(\"{s}\")"))?;
106//!     Ok(x)
107//! }
108//!
109//! fn outer(s: &str) -> Result<u64> {
110//!     // ...
111//!
112//!     let x = inner(s).stack()?;
113//!
114//!     // ...
115//!     Ok(u64::from(x))
116//! }
117//!
118//! let res = format!("{:?}", outer("valid"));
119//! assert_eq!(res, "Ok(42)");
120//!
121//! // The line numbers are slightly off because this is a doc test.
122//! // In order from outer to the innermost call, it lists the location of the
123//! // `stack` call from `outer`, the location of `stack_err` from `inner`,
124//! // the associated error message, the location of either the `Error::from`
125//! // or `stack_err` from `innermost`, and finally the root error message.
126//!
127//! // note that debug mode (used when returning errors from the main function)
128//! // includes terminal styling
129//! println!("{:?}", outer("return error"));
130//! let res = format!("{}", outer("return error").unwrap_err());
131//! assert_eq!(
132//!     res,
133//!     r#"
134//!   at src/lib.rs 45:22
135//!     error from innermost("return error") at src/lib.rs 38:10
136//!     bottom level `StrErr` at src/lib.rs 12:9"#
137//! );
138//!
139//! println!("{:?}", outer("parse invalid"));
140//! let res = format!("{}", outer("parse invalid").unwrap_err());
141//! assert_eq!(
142//!     res,
143//!     r#"
144//!   at src/lib.rs 45:22
145//!     error from innermost("parse invalid") at src/lib.rs 38:10
146//!     parsing error with "parse invalid" at src/lib.rs 24:14
147//!     1:1: Expected unit"#
148//! );
149//! ```
150//!
151//! ```text
152//! // in commonly used functions you may want `_locationless` to avoid adding
153//! // on unnecessary information if the location is already being added on
154//! return Err(e.add_err_locationless(ErrorKind::TimeoutError)).stack_err_with(|| {
155//!     format!(
156//!         "wait_for_ok(num_retries: {num_retries}, delay: {delay:?}) timeout, \
157//!          last error stack was:"
158//!     )
159//! })
160//! ```
161
162#![no_std]
163
164extern crate alloc;
165mod error;
166mod fmt;
167mod macros;
168mod special;
169mod stackable_err;
170
171pub use error::{Error, StackableErrorTrait, StackedError, StackedErrorDowncast};
172pub use fmt::{shorten_location, DisplayStr};
173pub use special::*;
174pub use stackable_err::StackableErr;
175
176/// A shorthand for [core::result::Result<T, stacked_errors::Error>]
177pub type Result<T> = core::result::Result<T, Error>;
178
179/// used by the macros
180#[doc(hidden)]
181pub mod __private {
182    pub use alloc::format;
183    pub use core::{concat, format_args, stringify};
184
185    #[track_caller]
186    pub fn format_err(args: core::fmt::Arguments<'_>) -> crate::Error {
187        let fmt_arguments_as_str = args.as_str();
188
189        if let Some(message) = fmt_arguments_as_str {
190            // &'static str
191            crate::Error::from_err(message)
192        } else {
193            // interpolation
194            crate::Error::from_err(alloc::fmt::format(args))
195        }
196    }
197
198    pub fn format_err_locationless(args: core::fmt::Arguments<'_>) -> crate::Error {
199        let fmt_arguments_as_str = args.as_str();
200
201        if let Some(message) = fmt_arguments_as_str {
202            // &'static str
203            crate::Error::from_err_locationless(message)
204        } else {
205            // interpolation
206            crate::Error::from_err_locationless(alloc::fmt::format(args))
207        }
208    }
209}