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}