erratic 0.1.2

Handling errors in an efficient way.
Documentation

Erratic

license crates.io docs.rs

This library provides the Error<S = Stateless> type, enabling applications to handle errors uniformly across different contexts.

Basic Usage

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 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 file"))
        .with_payload(|| filename)?; // One alloc to store both `io::Error` and `filename`.
    Ok(())
}

Binding State

When propagating an error that needs special handling, you can supply a generic state alongside it. For the consumer handling this error, the state must be explicitly erased via erase() before it can be further propagated upward. If the state implements Default, other errors can be wrapped and returned directly through ?.

When no error is wrapped and no context/payload is attached, the state is inlined without triggering any heap allocation. On 32-bit targets, the error stays at 1 usize when the state is no larger than 2 bytes; on 64-bit targets, it stays at 1 usize when the state is no larger than 4 bytes.

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 internal 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 discriminant. This makes it possible to avoid heap allocation when not needed.

(32-bit platform, little-endian)
(Context)
[XXXXXX00|XXXXXXXX|XXXXXXXX|XXXXXXXX]
                                     \
                                      `rodata-> [&'static str] --rodata--> [ ~ str ~ ]
(Error, Payload, or State & Context)
[XXXXXX01|XXXXXXXX|XXXXXXXX|XXXXXXXX]
          \
           `heap-> [ ~ State/() ~ | ~ VTable ~ | ~ Error ~ | ~ Payload/() ~ |&'static str/()]
(State)
[00000010|     ~    State     ~     ]

Contribution

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.