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}