stacked_errors/
error.rs

1use core::{
2    any::Any,
3    fmt::{Debug, Display},
4    panic::Location,
5    slice::{Iter, IterMut},
6};
7
8use smallbox::{smallbox, SmallBox};
9use thin_vec::{thin_vec, ThinVec};
10
11use crate::{ProbablyNotRootCauseError, TimeoutError, UnitError};
12
13/// Trait implemented for all `T: Display + Send + Sync + 'static`
14///
15/// This is a clever workaround from
16/// <https://users.rust-lang.org/t/impossible-to-use-any-combined-with-any-other-trait/85949/5>
17/// needed to enable using a type in both `dyn Display` form for displaying and
18/// in `dyn Any + Send + Sync` form for later downcasting
19pub trait StackableErrorTrait: Display + Any + Send + Sync + 'static {
20    // put as underscores and with `#[doc(hidden)]` since this this a hack
21    // implemented for all `T` that we don't want in IDEs
22    #[doc(hidden)]
23    fn _as_any(&self) -> &(dyn Any + Send + Sync);
24    #[doc(hidden)]
25    fn _as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync);
26    #[doc(hidden)]
27    fn _as_display(&self) -> &(dyn Display + Send + Sync);
28}
29
30impl<T: Display + Send + Sync + 'static> StackableErrorTrait for T {
31    fn _as_any(&self) -> &(dyn Any + Send + Sync) {
32        self
33    }
34
35    fn _as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) {
36        self
37    }
38
39    fn _as_display(&self) -> &(dyn Display + Send + Sync) {
40        self
41    }
42}
43
44pub trait StackedErrorDowncast: StackableErrorTrait + Sized {
45    fn get_err(&self) -> &(impl Display + Send + Sync + 'static);
46
47    fn get_location(&self) -> Option<&'static Location<'static>>;
48
49    // TODO the `eyre` crate has found a way to get this
50    // to work without the result being boxed
51
52    // Attempts to downcast to a concrete type.
53    //fn downcast<E: Display + Send + Sync + 'static>(self) -> Result<E, Self>;
54
55    fn downcast_ref<E>(&self) -> Option<&E>
56    where
57        E: Display + Send + Sync + 'static;
58
59    fn downcast_mut<E>(&mut self) -> Option<&mut E>
60    where
61        E: Display + Send + Sync + 'static;
62}
63
64/// NOTE: this type is only public because `impl Trait` in associated types is
65/// unstable, only `StackedErrorDowncast` methods are intended to be used on
66/// this.
67// The specific type that `Error` uses in its stack. NOTE the `error_kind_size`
68// should be updated whenever this is changed. pub type ErrorBox = Box<dyn
69// Display + Send + Sync + 'static>;
70pub struct ErrorItem {
71    b: SmallBox<dyn StackableErrorTrait, smallbox::space::S4>,
72    l: Option<&'static Location<'static>>,
73}
74
75#[cfg(target_pointer_width = "64")]
76#[test]
77fn error_kind_size() {
78    assert_eq!(core::mem::size_of::<ErrorItem>(), 56);
79}
80
81impl ErrorItem {
82    pub fn new<E: Display + Send + Sync + 'static>(
83        e: E,
84        l: Option<&'static Location<'static>>,
85    ) -> Self {
86        Self { b: smallbox!(e), l }
87    }
88}
89
90impl Debug for ErrorItem {
91    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92        f.write_fmt(format_args!("{}", self.get_err()))?;
93        if let Some(location) = self.get_location() {
94            f.write_fmt(format_args!(" {location:?}"))?;
95        }
96        Ok(())
97    }
98}
99
100impl Display for ErrorItem {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        core::fmt::Debug::fmt(self, f)
103    }
104}
105
106impl StackedErrorDowncast for ErrorItem {
107    fn get_err(&self) -> &(impl Display + Send + Sync + 'static) {
108        &self.b
109    }
110
111    fn get_location(&self) -> Option<&'static Location<'static>> {
112        self.l
113    }
114
115    //fn downcast<E: Display + Send + Sync + 'static>(self) -> Result<E, Self> {
116    //    self.0.as_any().
117    //}
118
119    // the borrows here are actually needed for `_as_any` to get the correct thing
120    #[allow(clippy::needless_borrow)]
121    fn downcast_ref<E>(&self) -> Option<&E>
122    where
123        E: Display + Send + Sync + 'static,
124    {
125        (&*self.b)._as_any().downcast_ref()
126    }
127
128    #[allow(clippy::needless_borrow)]
129    fn downcast_mut<E>(&mut self) -> Option<&mut E>
130    where
131        E: Display + Send + Sync + 'static,
132    {
133        (&mut self.b)._as_any_mut().downcast_mut()
134    }
135}
136
137/// An error struct intended for high level error propogation with programmable
138/// backtraces
139///
140/// For lower level error propogation, you should still use ordinary [Option]
141/// and [Result] with domain-specific enums, it is only when using OS-level
142/// functions or when multiple domains converge that this is intended to be
143/// used. This has an internal stack for different kinds of arbitrary errors and
144/// [Location](core::panic::Location)s. When used with the
145/// [StackableErr](crate::StackableErr) trait, this enables easy conversion and
146/// software defined backtraces for better `async` debugging. See the crate docs
147/// for more.
148pub struct StackedError {
149    /// Using a ThinVec has advantages such as taking as little space as
150    /// possible on the stack (since we are commiting to some indirection at
151    /// this point), and having the niche optimizations applied to things like
152    /// `Result<(), Error>`.
153    stack: ThinVec<ErrorItem>,
154}
155
156pub type Error = StackedError;
157
158/// Note: in most cases you can use `Error::from` or a call from `StackableErr`
159/// instead of these functions.
160impl Error {
161    /// Returns an empty error stack
162    pub fn empty() -> Self {
163        Self {
164            stack: ThinVec::new(),
165        }
166    }
167
168    /// Returns an error stack with just a `UnitError` and location information
169    #[track_caller]
170    pub fn new() -> Self {
171        Self::from_err(UnitError {})
172    }
173
174    #[track_caller]
175    pub fn from_err<E: Display + Send + Sync + 'static>(e: E) -> Self {
176        Self {
177            stack: thin_vec![ErrorItem::new(e, Some(Location::caller()))],
178        }
179    }
180
181    pub fn from_err_locationless<E: Display + Send + Sync + 'static>(e: E) -> Self {
182        Self {
183            stack: thin_vec![ErrorItem::new(e, None)],
184        }
185    }
186
187    /// Only pushes `track_caller` location to the stack
188    #[track_caller]
189    pub fn push(&mut self) {
190        self.push_err(UnitError {})
191    }
192
193    /// Only adds `track_caller` location to the stack
194    #[track_caller]
195    pub fn add(self) -> Self {
196        self.add_err(UnitError {})
197    }
198
199    /// Pushes error `e` with location to the stack
200    #[track_caller]
201    pub fn push_err<E: Display + Send + Sync + 'static>(&mut self, e: E) {
202        self.stack.push(ErrorItem::new(e, Some(Location::caller())));
203    }
204
205    /// Adds error `e` with location to the stack
206    #[track_caller]
207    pub fn add_err<E: Display + Send + Sync + 'static>(mut self, e: E) -> Self {
208        self.push_err(e);
209        self
210    }
211
212    /// Pushes error `e` without location information to the stack
213    pub fn push_err_locationless<E: Display + Send + Sync + 'static>(&mut self, e: E) {
214        self.stack.push(ErrorItem::new(e, None));
215    }
216
217    /// Adds error `e` without location information to the stack
218    pub fn add_err_locationless<E: Display + Send + Sync + 'static>(mut self, e: E) -> Self {
219        self.push_err_locationless(e);
220        self
221    }
222
223    /// Moves the stack of `other` onto `self`
224    pub fn chain_errors(mut self, mut other: Self) -> Self {
225        self.stack.append(&mut other.stack);
226        self
227    }
228
229    /// Returns a base `TimeoutError` error
230    #[track_caller]
231    pub fn timeout() -> Self {
232        Self::from_err(TimeoutError {})
233    }
234
235    /// Returns a base `ProbablyNotRootCauseError` error
236    #[track_caller]
237    pub fn probably_not_root_cause() -> Self {
238        Self::from_err(ProbablyNotRootCauseError {})
239    }
240
241    /// Returns if a `TimeoutError` is in the error stack
242    pub fn is_timeout(&self) -> bool {
243        for e in &self.stack {
244            if e.downcast_ref::<TimeoutError>().is_some() {
245                return true
246            }
247        }
248        false
249    }
250
251    /// Returns if a `ProbablyNotRootCauseError` is in the error stack
252    pub fn is_probably_not_root_cause(&self) -> bool {
253        for e in &self.stack {
254            if e.downcast_ref::<ProbablyNotRootCauseError>().is_some() {
255                return true
256            }
257        }
258        false
259    }
260
261    /// Iteration over the [StackedErrorDowncast] items of `self`
262    pub fn iter(&self) -> Iter<ErrorItem> {
263        self.stack.iter()
264    }
265
266    /// Mutable iteration over the [StackedErrorDowncast] items of `self`
267    pub fn iter_mut(&mut self) -> IterMut<ErrorItem> {
268        self.stack.iter_mut()
269    }
270}
271
272impl<'a> IntoIterator for &'a Error {
273    type IntoIter = Iter<'a, ErrorItem>;
274    type Item = &'a ErrorItem;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.iter()
278    }
279}
280
281impl<'a> IntoIterator for &'a mut Error {
282    type IntoIter = IterMut<'a, ErrorItem>;
283    type Item = &'a mut ErrorItem;
284
285    fn into_iter(self) -> Self::IntoIter {
286        self.iter_mut()
287    }
288}
289
290impl Default for Error {
291    #[track_caller]
292    fn default() -> Self {
293        Error::new()
294    }
295}
296
297impl core::error::Error for Error {}
298
299// there is a blanket impl collision, but I don't think we want to impl this
300// anyway since without it it makes sure we have `stack` calls at the error
301// origin
302/* impl<E: Display + Send + Sync + 'static> From<E> for Error */