Skip to main content

rootcause/
lib.rs

1#![cfg_attr(not(doc), no_std)]
2#![deny(
3    missing_docs,
4    elided_lifetimes_in_paths,
5    clippy::alloc_instead_of_core,
6    clippy::std_instead_of_alloc,
7    clippy::std_instead_of_core,
8    clippy::missing_safety_doc,
9    clippy::undocumented_unsafe_blocks,
10    clippy::multiple_unsafe_ops_per_block,
11    clippy::as_ptr_cast_mut,
12    clippy::ptr_as_ptr,
13    rustdoc::invalid_rust_codeblocks,
14    rustdoc::broken_intra_doc_links,
15    missing_copy_implementations,
16    unused_doc_comments
17)]
18// Extra checks on nightly
19#![cfg_attr(nightly_extra_checks, feature(rustdoc_missing_doc_code_examples))]
20#![cfg_attr(nightly_extra_checks, forbid(rustdoc::missing_doc_code_examples))]
21// Make docs.rs generate better docs
22#![cfg_attr(docsrs, feature(doc_cfg))]
23
24//! A flexible, ergonomic, and inspectable error reporting library for Rust.
25//!
26//! <img src="https://github.com/rootcause-rs/rootcause/raw/main/rootcause.png" width="192">
27//!
28//! ## Overview
29//!
30//! This crate provides a structured way to represent and work with errors and
31//! their context. The main goal is to enable you to build rich, structured
32//! error reports that automatically capture not just what went wrong, but also
33//! the context and supporting data at each step in the error's propagation.
34//!
35//! Unlike simple string-based error messages, rootcause allows you to attach
36//! typed data to errors, build error chains, and inspect error contents
37//! programmatically. This makes debugging easier while still providing
38//! beautiful, human-readable error messages.
39//!
40//! ## Quick Example
41//!
42//! ```
43//! use rootcause::prelude::{Report, ResultExt};
44//!
45//! fn read_config(path: &str) -> Result<String, Report> {
46//!     std::fs::read_to_string(path).context("Failed to read configuration file")?;
47//!     Ok(String::new())
48//! }
49//! ```
50//!
51//! For more examples, see the
52//! [examples directory](https://github.com/rootcause-rs/rootcause/tree/main/examples)
53//! in the repository. Start with
54//! [`basic.rs`](https://github.com/rootcause-rs/rootcause/blob/main/examples/basic.rs)
55//! for a hands-on introduction.
56//!
57//! ## Core Concepts
58//!
59//! At a high level, rootcause helps you build a tree of error reports. Each
60//! node in the tree represents a step in the error's history - you start with a
61//! root error, then add context and attachments as it propagates up through
62//! your code.
63//!
64//! Most error reports are linear chains (just like anyhow), but the tree
65//! structure lets you collect multiple related errors when needed.
66//!
67//! Each report has:
68//! - A **context** (the error itself)
69//! - Optional **attachments** (debugging data)
70//! - Optional **children** (one or more errors that caused this error)
71//!
72//! For implementation details, see the [`rootcause-internals`] crate.
73//!
74//! [`rootcause-internals`]: rootcause_internals
75//!
76//! ## Ecosystem
77//!
78//! rootcause is designed to be lightweight and extensible. The core library
79//! provides essential error handling, while optional companion crates add
80//! specialized capabilities:
81//!
82//! - **[`rootcause-backtrace`]** - Automatic stack trace capture for debugging.
83//!   Install hooks to attach backtraces to all errors, or use the extension
84//!   trait to add them selectively.
85//!
86//! [`rootcause-backtrace`]: https://docs.rs/rootcause-backtrace
87//!
88//! ## Project Goals
89//!
90//! - **Ergonomic**: The `?` operator should work with most error types, even
91//!   ones not designed for this library.
92//! - **Multi-failure tracking**: When operations fail multiple times (retry
93//!   attempts, batch processing, parallel execution), all failures should be
94//!   captured and preserved in a single report.
95//! - **Inspectable**: The objects in a Report should not be glorified strings.
96//!   Inspecting and interacting with them should be easy.
97//! - **Optionally typed**: Users should be able to (optionally) specify the
98//!   type of the context in the root node.
99//! - **Beautiful**: The default formatting should look pleasant—and if it
100//!   doesn't match your style, the [hook system] lets you customize it.
101//! - **Cloneable**: It should be possible to clone a [`Report`] when you need
102//!   to.
103//! - **Self-documenting**: Reports should automatically capture information
104//!   (like locations) that might be useful in debugging. Additional
105//!   instrumentation like backtraces can be added via extension crates.
106//! - **Customizable**: It should be possible to customize what data gets
107//!   collected, or how reports are formatted.
108//! - **Lightweight**: [`Report`] has a pointer-sized representation, keeping
109//!   `Result<T, Report>` small and fast.
110//!
111//! [hook system]: crate::hooks
112//!
113//! ## Report Type Parameters
114//!
115//! The [`Report`] type is generic over three parameters, but for most users the
116//! defaults work fine.
117//!
118//! **Most common usage:**
119//!
120//! ```
121//! # use rootcause::prelude::*;
122//! // Just use Report - works like anyhow::Error
123//! fn might_fail() -> Result<(), Report> {
124//!     # Ok(())
125//! }
126//! ```
127//!
128//! **For type safety:**
129//!
130//! ```
131//! # use rootcause::prelude::*;
132//! #[derive(Debug)]
133//! struct MyError;
134//! # impl std::fmt::Display for MyError {
135//! #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136//! #         write!(f, "MyError")
137//! #     }
138//! # }
139//! # impl std::error::Error for MyError {}
140//!
141//! // Use Report<YourError> - works like error-stack
142//! fn typed_error() -> Result<(), Report<MyError>> {
143//!     # Ok(())
144//! }
145//! ```
146//!
147//! **Need cloning or thread-local data?** The sections below explain the other
148//! type parameters. Come back to these when you need them - they solve specific
149//! problems you'll recognize when you encounter them.
150//!
151//! ---
152//!
153//! ## Type Parameters
154//!
155//! *This section covers the full type parameter system. Most users won't need
156//! these variants immediately - but if you do need cloning, thread-local
157//! errors, or want to understand what's possible, read on.*
158//!
159//! The [`Report`] type has three type parameters: `Report<Context, Ownership,
160//! ThreadSafety>`. This section explains all the options and when you'd use
161//! them.
162//!
163//! ### Context Type: Typed vs Dynamic Errors
164//!
165//! **Use `Report<Dynamic>`** (or just [`Report`]) when errors just need to
166//! propagate. **Use `Report<YourErrorType>`** when callers need to pattern
167//! match on specific error variants.
168//!
169//! **`Report<Dynamic>`** (or just [`Report`]) — Flexible, like [`anyhow`]
170//!
171//! Can hold any error type at the root. The `?` operator automatically converts
172//! any error into a [`Report`]. Note: [`Dynamic`] is just a marker signaling
173//! that the actual type is unknown. No actual instance of [`Dynamic`] is
174//! stored. Converting between typed and dynamic reports is zero-cost.
175//!
176//! [`Dynamic`]: crate::markers::Dynamic
177//!
178//! ```
179//! # use rootcause::prelude::*;
180//! // Can return any error type
181//! fn might_fail() -> Result<(), Report> {
182//!     # Ok(())
183//! }
184//! ```
185//!
186//! **`Report<YourErrorType>`** — Type-safe, like [`error-stack`]
187//!
188//! The root error must be `YourErrorType`, but child errors can be anything.
189//! Callers can use `.current_context()` to pattern match on the typed error.
190//!
191//! ```
192//! # use rootcause::prelude::*;
193//! #[derive(Debug)]
194//! struct ConfigError {/* ... */}
195//! # impl std::fmt::Display for ConfigError {
196//! #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
197//! # }
198//! # impl std::error::Error for ConfigError {}
199//!
200//! // This function MUST return ConfigError at the root
201//! fn load_config() -> Result<(), Report<ConfigError>> {
202//!     # Ok(())
203//! }
204//! ```
205//!
206//! See [`examples/typed_reports.rs`] for a complete example with retry logic.
207//!
208//! [`examples/typed_reports.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/typed_reports.rs
209//!
210//! ### Ownership: Mutable vs Cloneable
211//!
212//! **Use the default ([`Mutable`])** when errors just propagate with `?`.
213//! **Use [`.into_cloneable()`]** when you need to store errors in collections
214//! or use them multiple times.
215//!
216//! [`.into_cloneable()`]: crate::report::owned::Report::into_cloneable
217//!
218//! **[`Mutable`]** (default) — Unique ownership
219//!
220//! You can add attachments and context to the root, but can't clone the whole
221//! [`Report`]. Note: child reports are still cloneable internally (they use
222//! `Arc`), but the top-level [`Report`] doesn't implement `Clone`. Start here,
223//! then convert to [`Cloneable`] if you need to clone the entire tree.
224//!
225//! ```
226//! # use rootcause::prelude::*;
227//! let mut report: Report<String, markers::Mutable> = report!("error".to_string());
228//! let report = report.attach("debug info"); // ✅ Can mutate root
229//! // let cloned = report.clone();           // ❌ Can't clone whole report
230//! ```
231//!
232//! **[`Cloneable`]** — Shared ownership
233//!
234//! The [`Report`] can be cloned cheaply (via `Arc`), but can't be mutated. Use
235//! when you need to pass the same error to multiple places.
236//!
237//! ```
238//! # use rootcause::prelude::*;
239//! let report: Report<String, markers::Mutable> = report!("error".to_string());
240//! let cloneable = report.into_cloneable();
241//! let copy1 = cloneable.clone(); // ✅ Can clone
242//! let copy2 = cloneable.clone(); // ✅ Cheap (Arc clone)
243//! // let modified = copy1.attach("info"); // ❌ Can't mutate
244//! ```
245//!
246//! See [`examples/retry_with_collection.rs`] for collection usage.
247//!
248//! [`examples/retry_with_collection.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/retry_with_collection.rs
249//!
250//! ### Thread Safety: SendSync vs Local
251//!
252//! **Use the default ([`SendSync`])** unless you get compiler errors about
253//! `Send` or `Sync`. **Use [`Local`]** only when attaching `!Send` types like
254//! `Rc` or `Cell`.
255//!
256//! **[`SendSync`]** (default) — Thread-safe
257//!
258//! The [`Report`] and all its contents are `Send + Sync`. Most types (String,
259//! Vec, primitives) are already `Send + Sync`, so this just works.
260//!
261//! ```
262//! # use rootcause::prelude::*;
263//! let report: Report<String, markers::Mutable, markers::SendSync> = report!("error".to_string());
264//!
265//! # let thread_join_handle =
266//! std::thread::spawn(move || {
267//!     println!("{}", report); // ✅ Can send to other threads
268//! });
269//! # thread_join_handle.join();
270//! ```
271//!
272//! **[`Local`]** — Not thread-safe
273//!
274//! Use when your error contains thread-local data like `Rc`, raw pointers, or
275//! other `!Send` types.
276//!
277//! ```
278//! # use rootcause::prelude::*;
279//! use std::rc::Rc;
280//!
281//! let data = Rc::new("thread-local".to_string());
282//! let report: Report<Rc<String>, markers::Mutable, markers::Local> = report!(data);
283//! // std::thread::spawn(move || { ... }); // ❌ Can't send to other threads
284//! ```
285//!
286//! ## Converting Between Report Variants
287//!
288//! The variant lists above have been ordered so that it is always possible to
289//! convert to an element further down the list using the [`From`] trait. This
290//! also means you can use `?` when converting downwards. There are also more
291//! specific methods (implemented using [`From`]) to help with type inference
292//! and to more clearly communicate intent:
293//!
294//! - [`Report::into_dynamic`] converts from `Report<C, *, *>` to
295//!   `Report<Dynamic, *, *>`. See [`examples/error_coercion.rs`] for usage
296//!   patterns.
297//! - [`Report::into_cloneable`] converts from `Report<*, Mutable, *>` to
298//!   `Report<*, Cloneable, *>`. See [`examples/retry_with_collection.rs`] for
299//!   storing multiple errors.
300//! - [`Report::into_local`] converts from `Report<*, *, SendSync>` to
301//!   `Report<*, *, Local>`.
302//!
303//! On the other hand, it is generally harder to convert to an element further
304//! up the list. Here are some of the ways to do it:
305//!
306//! - From `Report<Dynamic, *, *>` to `Report<SomeContextType, *, *>`:
307//!   - You can check if the type of the root node matches a specific type by
308//!     using [`Report::downcast_report`]. This will return either the requested
309//!     report type or the original report depending on whether the types match.
310//!     See [`examples/inspecting_errors.rs`] for downcasting techniques.
311//! - From `Report<*, Cloneable, *>` to `Report<*, Mutable, *>`:
312//!   - You can check if the root node only has a single owner using
313//!     [`Report::try_into_mutable`]. This will check the number of references
314//!     to the root node and return either the requested report variant or the
315//!     original report depending on whether it is unique.
316//!   - You can allocate a new root node and set the current node as a child of
317//!     the new node. The new root node will be [`Mutable`]. One method for
318//!     allocating a new root node is to call [`Report::context`].
319//! - From `Report<*, *, *>` to `Report<PreformattedContext, Mutable,
320//!   SendSync>`:
321//!   - You can preformat the entire [`Report`] using [`Report::preformat`].
322//!     This creates an entirely new [`Report`] that has the same structure and
323//!     will look the same as the current one if printed, but all contexts and
324//!     attachments will be replaced with a [`PreformattedContext`] version.
325//!
326//! [`examples/error_coercion.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/error_coercion.rs
327//! [`examples/inspecting_errors.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/inspecting_errors.rs
328//!
329//! # Acknowledgements
330//!
331//! This library was inspired by and draws ideas from several existing error
332//! handling libraries in the Rust ecosystem, including [`anyhow`],
333//! [`thiserror`], and [`error-stack`].
334//!
335//! [`PreformattedContext`]: crate::preformatted::PreformattedContext
336//! [`Mutable`]: crate::markers::Mutable
337//! [`Cloneable`]: crate::markers::Cloneable
338//! [`SendSync`]: crate::markers::SendSync
339//! [`Local`]: crate::markers::Local
340//! [`anyhow`]: https://docs.rs/anyhow
341//! [`anyhow::Error`]: https://docs.rs/anyhow/latest/anyhow/struct.Error.html
342//! [`thiserror`]: https://docs.rs/thiserror
343//! [`error-stack`]: https://docs.rs/error-stack
344//! [`error-stack::Report`]: https://docs.rs/error-stack/latest/error_stack/struct.Report.html
345
346extern crate alloc;
347
348#[macro_use]
349mod macros;
350
351pub mod handlers;
352pub mod hooks;
353pub mod markers;
354pub mod preformatted;
355
356pub mod compat;
357pub mod option_ext;
358pub mod prelude;
359mod report;
360pub mod report_attachment;
361pub mod report_attachments;
362pub mod report_collection;
363
364mod into_report;
365mod iterator_ext;
366mod report_conversion;
367mod result_ext;
368mod util;
369
370pub use self::{
371    into_report::{IntoReport, IntoReportCollection},
372    report::{iter::ReportIter, mut_::ReportMut, owned::Report, ref_::ReportRef},
373    report_conversion::ReportConversion,
374};
375
376/// A [`Result`](core::result::Result) type alias where the error is [`Report`].
377///
378/// This is a convenient shorthand for functions that return errors as
379/// [`Report`]. The context type defaults to [`Dynamic`].
380///
381/// # Examples
382///
383/// ```
384/// use rootcause::prelude::*;
385///
386/// fn might_fail() -> rootcause::Result<String> {
387///     Ok("success".to_string())
388/// }
389/// ```
390///
391/// With a typed error:
392///
393/// ```
394/// use rootcause::prelude::*;
395///
396/// #[derive(Debug)]
397/// struct MyError;
398/// # impl std::fmt::Display for MyError {
399/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400/// #         write!(f, "MyError")
401/// #     }
402/// # }
403/// # impl std::error::Error for MyError {}
404///
405/// fn typed_error() -> rootcause::Result<String, MyError> {
406///     Err(report!(MyError))
407/// }
408/// ```
409///
410/// [`Dynamic`]: crate::markers::Dynamic
411pub type Result<T, C = markers::Dynamic> = core::result::Result<T, Report<C>>;
412
413// Not public API. Referenced by macro-generated code and rootcause-backtrace.
414#[doc(hidden)]
415pub mod __private {
416    // Used by the rootcause-backtrace
417    pub const ROOTCAUSE_LOCATION: &core::panic::Location<'_> = core::panic::Location::caller();
418
419    use alloc::fmt;
420    #[doc(hidden)]
421    pub use alloc::format;
422    #[doc(hidden)]
423    pub use core::{format_args, result::Result::Err};
424
425    use crate::{
426        Report, handlers,
427        markers::{self, Dynamic},
428        report_attachment::ReportAttachment,
429    };
430
431    #[doc(hidden)]
432    #[inline]
433    #[cold]
434    #[must_use]
435    #[track_caller]
436    pub fn format_report(
437        args: fmt::Arguments<'_>,
438    ) -> Report<Dynamic, markers::Mutable, markers::SendSync> {
439        if let Some(message) = args.as_str() {
440            Report::new_sendsync_custom::<handlers::Display>(message).into_dynamic()
441        } else {
442            Report::new_sendsync_custom::<handlers::Display>(fmt::format(args)).into_dynamic()
443        }
444    }
445
446    #[doc(hidden)]
447    #[inline]
448    #[cold]
449    #[must_use]
450    #[track_caller]
451    pub fn format_report_attachment(
452        args: fmt::Arguments<'_>,
453    ) -> ReportAttachment<Dynamic, markers::SendSync> {
454        if let Some(message) = args.as_str() {
455            ReportAttachment::new_sendsync_custom::<handlers::Display>(message).into_dynamic()
456        } else {
457            ReportAttachment::new_sendsync_custom::<handlers::Display>(fmt::format(args))
458                .into_dynamic()
459        }
460    }
461
462    #[doc(hidden)]
463    pub mod kind {
464        use crate::{
465            Report, handlers, markers, report_attachment::ReportAttachment,
466            report_attachments::ReportAttachments, report_collection::ReportCollection,
467        };
468
469        #[doc(hidden)]
470        pub struct Wrap<'a, T>(pub &'a T);
471
472        #[doc(hidden)]
473        pub trait SendSyncKind {
474            #[inline(always)]
475            fn thread_safety(&self) -> markers::SendSync {
476                markers::SendSync
477            }
478        }
479
480        impl<C> SendSyncKind for C where C: markers::ObjectMarkerFor<markers::SendSync> {}
481
482        #[doc(hidden)]
483        pub trait LocalKind {
484            #[inline(always)]
485            fn thread_safety(&self) -> markers::Local {
486                markers::Local
487            }
488        }
489
490        impl<C> LocalKind for &C where C: markers::ObjectMarkerFor<markers::Local> {}
491
492        #[doc(hidden)]
493        pub trait HandlerErrorKind {
494            #[inline(always)]
495            fn handler(&self) -> handlers::Error {
496                handlers::Error
497            }
498        }
499
500        impl<C> HandlerErrorKind for &&&Wrap<'_, C> where handlers::Error: handlers::ContextHandler<C> {}
501
502        #[doc(hidden)]
503        pub trait HandlerDisplayKind {
504            #[inline(always)]
505            fn handler(&self) -> handlers::Display {
506                handlers::Display
507            }
508        }
509
510        impl<C> HandlerDisplayKind for &&Wrap<'_, C> where handlers::Display: handlers::ContextHandler<C> {}
511
512        #[doc(hidden)]
513        pub trait HandlerDebugKind {
514            #[inline(always)]
515            fn handler(&self) -> handlers::Debug {
516                handlers::Debug
517            }
518        }
519
520        impl<C> HandlerDebugKind for &Wrap<'_, C> where handlers::Debug: handlers::ContextHandler<C> {}
521
522        #[doc(hidden)]
523        pub trait HandlerAnyKind {
524            #[inline(always)]
525            fn handler(&self) -> handlers::Any {
526                handlers::Any
527            }
528        }
529
530        impl<C> HandlerAnyKind for Wrap<'_, C> where handlers::Any: handlers::ContextHandler<C> {}
531
532        #[doc(hidden)]
533        #[must_use]
534        #[track_caller]
535        pub fn macro_helper_new_report<H, T, C>(
536            _handler: H,
537            _thread_safety: T,
538            context: C,
539        ) -> Report<C, markers::Mutable, T>
540        where
541            H: handlers::ContextHandler<C>,
542            C: markers::ObjectMarkerFor<T>,
543        {
544            Report::from_parts::<H>(context, ReportCollection::new(), ReportAttachments::new())
545        }
546
547        #[doc(hidden)]
548        #[must_use]
549        #[track_caller]
550        pub fn macro_helper_new_report_attachment<H, T, A>(
551            _handler: H,
552            _thread_safety: T,
553            attachment: A,
554        ) -> ReportAttachment<A, T>
555        where
556            H: handlers::AttachmentHandler<A>,
557            A: markers::ObjectMarkerFor<T>,
558        {
559            ReportAttachment::new_custom::<H>(attachment)
560        }
561    }
562}