Expand description
This library provides Error<S = Stateless>, an error type with optional dynamic dispatch,
enabling applications to handle errors uniformly across different scenarios.
§Quick Start
In most cases, Error can serve as a drop-in replacement for Box<dyn Error>.
Compared to the latter, it occupies only 1 usize and eliminates allocations
altogether when constructed from a literal string or a small state.
fn say_hello(filename: &str) -> erratic::Result<()> {
File::open(filename)?.write_all(b"Hello, World!")?;
Ok(())
}§Attaching Context
When constructing an error, you can optionally attach a context. A literal string context with no other components incurs no heap allocation.
use erratic::*;
fn read_weak(r: &mut Weak<Reader>, buf: &mut [u8]) -> Result<u64> {
if buf.is_empty() {
return mkres!("buf must not be empty"); // No alloc so long as no format args.
}
let r = r.upgrade()
.with_context("stream expired")?; // Accepts any value implementing `Display`.
let n = r.read(buf)
.with_context(mkctx!("failed to read from {}", r.id()))?; // `mkctx!` evaluates lazily.
Ok(n)
}§Binding State
When propagating domain errors, you can optionally attach a state to it. A small state with no other components incurs no heap allocation.
use erratic::*;
#[derive(Debug)]
enum State { RetryLater } // Smaller than 1 usize.
fn try_write(w: &mut Writer, blk: &[u8; 64]) -> Result<(), Error<State>> {
w.reserve_chunk(64)
.ok()
.with_state(State::RetryLater)?; // No alloc.
w.write(blk)
.with_context(mkctx!("failed to write to {}", w.id()))?;
Ok(())
}When no runtime state is actually stored, errors can be cheaply converted between different state types.
This allows infrastructure errors to cross any number of layers with no extra allocation, domain errors
avoid the heap entirely, and both share the same Error<S> type. All compose orthogonally.
fn write(w: &mut Writer, blk: &[u8; 64]) -> Result<()> {
while let Err((state, _)) = try_write(w, blk).extract_state()? { // Bubble up infra errors.
match state { // Handle domain errors.
State::RetryLater => thread::yield_now(),
// ..
}
}
Ok(())
}The ? operator covers the most common cases, regardless of whether the return type carries state:
| Source Type | Return Type | Explanation |
|---|---|---|
impl Error | Error<_> | Wrap any standard error type. |
Builder<..> | Error<_> | Build an error from state, context, and/or source. |
Error<Stateless> | Error<S> | Cheaply convert a stateless error to a stateful one. |
States are meant to be handled explicitly. Several utility methods are provided:
| Method | Conversion | Explanation |
|---|---|---|
extract_state | Error<S> -> Result<(S, Vacant<S>), Error> | Take the state out, or propagate the error. |
erase_error | Error<S> -> impl Error | Erase the error along with its state. |
map_state | Error<S> -> Error<S2> | Transform the state with a closure. |
lift_state | Error<S> -> Error<S2> where S2: From<S> | Transform the state via From. |
§Formatting
If the error has a state and/or a context, it builds its message from them. Otherwise, it acts as an error container, inheriting the message from its source. When wrapped, the container itself will not be added as another source layer, preventing duplicate messages in the chain.
<error> ::= <source>
| <state>": "<context>
| <context>
| <state>
<chain> ::= <error>
| <error>"\n -> "<chain>By default, only the top-level error is shown during formatting. To display the full error chain, format with alternate or debug specifiers.
| Specifier | Explanation |
|---|---|
{} | Display only the top-level error. |
{:#} | Display the full error chain. |
{:?} | Display the full error chain with backtrace, if captured. |
{:#?} | Display all information in a struct-like format. |
§Custom Formatting
To customize the error message, use FormatWith<F> at the point of printing. Since the formatter is tied
to type rather than value, the rest of the program can use the error as usual, without thinking about
how it will be displayed.
For example:
fn main() -> Result<(), Error<FormatWith<Arrow>>> {
executor::block_on(async_main())?;
Ok(())
}
async fn async_main() -> erratic::Result<()> {
todo!();
}If async_main returns a chain of three errors, Arrow can format it as follows:
AppleNotFound: hoge
├─▶ failed to forage for food
└─▶ no such fruit§Backtrace
When the backtrace feature is enabled and backtrace capture is configured via
environment variables, Error<S> automatically captures a backtrace if there isn’t
one already in the source chain. The backtrace will be appended after the error chain during debug
formatting, unless the minus sign, e.g. {:-?}, is specified to suppress it.
§Representation
Type-wise, Error<S> is an internally tagged union, and it requires pointers to be aligned to 4 bytes,
freeing up the lower 2 bits to encode its discriminant. Pointer tagging in this crate fully follows
strict provenance, and is verified by Miri.
The error has three possible layouts. When constructed from a literal, it stores a pointer to the literal. When constructed from a small state, it stores the state inline. Otherwise, it points to a heap-allocated Object containing a vtable and potentially a state, source, and/or context.
┌Error<S>─────────────╎───┐ ┌ConstBody─────┐ ┌str──────┐
│ Align4Ref<ConstBody>╎00 ├───┤ ConstContext ├───┤ Literal │
└─────────────────────╎───┘ └──────────────┘ └─────────┘
┌Error<S>─────────────╎───┐ ┌BoxedBody─────────────┬────────────────────┬────────┬─────────┐
│ Align4Own<BoxedBody>╎01 ├───┤ Align4Ref<VTable>╎0H │ MaybeUninit<State> │ Source │ Context │
└─────────────────────╎───┘ └────────────────────┼─┴───────────────┼────┴────────┴─────────┘
┌Error<S>─────┬───────╎───┐ └──H=1:HasState───┘
│ State │ 000000╎10 │
└─────────────┴───────╎───┘Modules§
- builder
- Builder for constructing errors.
- context
- Context helpers and traits.
- fmt
- Trait for defining custom formatters.
- state
- State helpers and traits.
Macros§
- match_
else - Like
let-else, with access to variant bindings in other branches, forResultonly. - mkctx
- Creates a lazily-evaluated context from a format string.
- mkerr
- Constructs an
Errorfrom a variety of input types. - mkres
- Shorthand for
Err(mkerr!(..)).
Structs§
- Error
- An error type that can carry optional state, source, and context.
Traits§
- Builder
Ext - Extension trait for attaching context and state to an existing error.
- Error
Ext - Extension trait for materializing or erasing an error.
- State
Ext - Extension trait for working with the state.