erratic 0.3.1

Handling errors in an efficient way.
Documentation

Erratic /ɪˈrætɪk/

license crates.io docs.rs

This library provides Error<S = Stateless>, an optionally dynamic dispatched error type, enabling applications to handle errors uniformly across different contexts.

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, making the happy path faster.

use erratic::*;

fn write_log(filename: String) -> Result<()> {
    File::open(&filename)?.write_all(b"Hello, World!")?;
    Ok(())
}

Attaching Context & Payload

When constructing an error, you can optionally attach a static context and/or a dynamic payload. If attached, their memory is merged into a single allocation when the upstream error is erased. If omitted, no extra memory is allocated for them. If only a context is provided, no heap allocation occurs at all.

use erratic::*;

fn write_log(filename: String) -> Result<()> {
    File::open(&filename)
        .or_context(literal!("failed to open the log file"))? // No alloc.
        .write_all(b"Hello, World!")
        .with_context(literal!("while writing to"))
        .with_payload(filename)?; // Alloc once for `io::Error`, `filename`, and `Context`.
    Ok(())
}

Binding State

When propagating an error that requires special handling, you can supply a generic state alongside it. If the state implements Default, other errors can be wrapped and returned directly via ? without explicitly setting the state.

When the state is small enough and none of the source error, context, or payload is attached, the state is inlined without any heap allocation.

use erratic::*;

#[derive(Debug, Default)]
enum WriteLog {
    FileNotFound,
    #[default]
    Other,
}

fn write_log(filename: String) -> std::result::Result<(), Error<WriteLog>> {
    File::open(&filename)
        .or_state(WriteLog::FileNotFound)? // No alloc.
        .write_all(b"Hello, World!")
        .with_context(literal!("while writing to"))
        .with_payload(filename)?; // Falls back to the default state value.
    Ok(())
}

Representation

Type-wise, Error<S> is an internally tagged union, and it requires pointers to constant or heap-allocated data to be aligned to 4 bytes, freeing up the lower 2 bits to encode the discriminant. This design allows heap allocation to be avoided when unnecessary.

(32-bit platform, little-endian)
(Context)
[XXXXXX00|XXXXXXXX|XXXXXXXX|XXXXXXXX]
                                     \
                                      `rodata-> [&'static str]
(Small State)
[00000010|     ~    State     ~     ]

(Otherwise)
[XXXXXX01|XXXXXXXX|XXXXXXXX|XXXXXXXX]
       \
        `heap-> [ ~ State ~ |&'static VTable| ~ Error ~ | ~ Payload ~ |&'static str/()]

Contributing

Contributions are warmly welcomed! Whether you have a bug report, feature request, or an improvement in mind, feel free to open an issue or submit a pull request. All ideas—big or small—help make this library better for everyone.