rootcause/
lib.rs

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