ertrace/lib.rs
1//! **Experimental Error Return Tracing for Rust.**
2//!
3//! The immediate goals of this library are to:
4//! 1. provide a minimal-boilerplate error handling story based around error
5//! return tracing, and
6//! 2. demonstrate the value of error return tracing with the hopes of
7//! getting support directly integrated into the Rust compiler.
8//!
9//! This library is very much in its early days and is highly unstable. Some
10//! effort has been made to implement functionality with performance in mind,
11//! but, so far, no profiling has been performed. There is undoubtedly room for
12//! improvement.
13//!
14//! ## Error Return Tracing
15//!
16//! Error return tracing is a novel error handling concept developed by
17//! Andrew Kelley for the Zig programming language. Error return traces look a
18//! bit like the stack traces displayed by many popular programming languages
19//! when an exception goes uncaught. Stack traces provide extremely valuable
20//! information for identifying the source of an error, but, unfortunately, they
21//! have a considerable performance cost. For this reason, Rust only enables
22//! stack traces for panics, and only when the `RUST_BACKTRACE` environment
23//! variable is defined.
24//!
25//! Error return traces provide similar information to stack traces, but at
26//! a far smaller performance cost. They achieve this by tracing errors
27//! as they bubble up the call stack, rather than by capturing an entire
28//! stack trace when an error is first encountered. (For more information on
29//! the performance differences between stack traces and error return traces,
30//! please see the [section on performance](
31//! #performance-stack-traces-vs-error-return-traces), below.)
32//!
33//! Furthermore, error return traces can even provide *more* useful information
34//! than basic stack traces, since they trace where and why an error of one type
35//! causes an error of another type. Finally, since the errors are traced
36//! through each return point, error return tracing works seamlessly with
37//! M:N threading, futures, and async/await.
38//!
39//! ## Example
40//!
41//! ```rust,should_panic
42//! use ertrace::{ertrace, Ertrace};
43//!
44//! fn main() -> Result<(), AError> {
45//! // Forward any `AError` errors from `a`.
46//! a().map_err(|mut e| ertrace!(e =>))
47//! }
48//!
49//! fn a() -> Result<(), AError> {
50//! // On any error in `b`, return an `AError`, and trace the cause.
51//! b().map_err(|e| ertrace!(e => AError))?;
52//! Ok(())
53//! }
54//!
55//! fn b() -> Result<(), BError> {
56//! // Forward any `BError` errors from `b_inner`.
57//! b_inner().map_err(|mut e| ertrace!(e =>))
58//! }
59//!
60//! fn b_inner() -> Result<(), BError> {
61//! if true {
62//! // Initialize the traced error struct, `BError1`, and then use the `?`
63//! // operator to convert it into the appropriate `BError` enum instance
64//! // and return it.
65//! Err(ertrace!(BError1))?
66//! } else {
67//! // Initialize the traced error struct, `BError2`, and then use the `?`
68//! // operator to convert it into the appropriate `BError` enum instance
69//! // and return it.
70//! Err(ertrace!(BError2))?
71//! }
72//! }
73//!
74//! ertrace::new_error_types! {
75//! // Define new traced error structs `AError`, `BError1`, and `BError2`.
76//! pub struct AError(Ertrace);
77//! pub struct BError1(Ertrace);
78//! pub struct BError2(Ertrace);
79//!
80//! // Define a new traced error enum `BError`, with variants for
81//! // `BError1` and `BError2`.
82//! pub enum BError {
83//! BError1(BError1),
84//! BError2(BError2),
85//! }
86//! }
87//! ```
88//!
89//! Output:
90//!
91//! ```skip
92//! Error: AError
93//! error return trace:
94//! 0: BError1 at examples/basics.rs:24:13 in basics
95//! 1: => at examples/basics.rs:16:31 in basics
96//! 2: AError at examples/basics.rs:10:21 in basics
97//! 3: => at examples/basics.rs:5:25 in basics
98//! ```
99//!
100//! ## `no_std` Support
101//!
102//! Ertrace provides `no_std` support. By default, it depends on the `std` crate,
103//! in order to provide additional functionality, but
104//! this dependency is gated behind the `std` feature, and can be disabled by
105//! specifying `default-features = false` in your Cargo dependencies.
106//!
107//! Currently, the `alloc` crate is required, but it should be straight-forward to
108//! remove even that requirement by specifying a static block of memory in which
109//! to store error traces. If you have a need for this,
110//! [please open a Github issue](https://github.com/scottjmaddox/ertrace/issues/new).
111//!
112//! ## Performance: Stack Traces vs. Error Return Traces
113//!
114//! In order for a stack trace to be displayed when an exception goes uncaught, the
115//! entire stack trace must be captured when the exception is created (or when it is
116//! thrown/raised). This is a fairly expensive operation since it requires
117//! traversing each stack frame and storing (at minimum) a pointer for each function
118//! in the call stack, typically in some heap-allocated thread-local storage. The
119//! argument usually made is that exceptions should only be thrown in exceptional
120//! cases, and so the performance cost of collecting a stack trace will not
121//! significantly degrade the overall program performance. In reality, though,
122//! errors are quite common, and the cost of stack traces is not negligible.
123//!
124//! In contrast, the cost of error return tracing starts very small, and scales
125//! linearly with the number of times errors are returned. If an error is
126//! handled one stack frame above where it is first created, the overhead
127//! runtime cost can be as small as a few ALU ops and a single memory write
128//! (if you have compiler support... The runtime overhead for this library
129//! implementation is a bit higher).
130
131#![deny(missing_debug_implementations)]
132// #![deny(missing_docs)] // TODO: uncomment this
133#![cfg_attr(not(feature = "std"), no_std)]
134
135mod ertrace;
136mod ertrace_location;
137mod ertrace_macro;
138mod new_error_enum_macro;
139mod new_error_struct_macro;
140mod new_error_types_macro;
141#[cfg(test)]
142mod tests;
143
144pub use crate::ertrace::*;
145pub use crate::ertrace_location::*;
146pub use crate::ertrace_macro::*;
147pub use crate::new_error_enum_macro::*;
148pub use crate::new_error_struct_macro::*;
149pub use crate::new_error_types_macro::*;