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}