custom_error
A macro for defining custom Rust error enums and structs without hand-writing Display and Error impls.
Repository Health
Technical Analysis
custom_error provides a single declarative macro, custom_error!, that expands a compact list of error cases into a full Rust enum (or a struct, for single-case errors) with generated Display, Debug, and std::error::Error implementations. Field values are interpolated directly into error messages using {field_name} syntax, and fields named source are automatically wired into the source() method along with a generated From<SourceType> conversion, so the ? operator can convert foreign errors into your custom type without extra code.
Unlike proc-macro-based alternatives, custom_error is implemented entirely with macro_rules!, so it adds no proc-macro, syn, or quote dependency to a project’s build. It also supports no_std environments (disabling the default std feature falls back to a bundled Error trait for core/alloc), making it usable in embedded and other constrained contexts where crates like thiserror are unavailable.
What You Get
- The
custom_error!macro, which generates an enum (multiple cases) or struct (single case) plus itsDisplay,Debug, andErrortrait implementations - Interpolated error messages that reference variant fields with
{field_name}syntax, resolved at Display time - Automatic
source()wiring and generatedFrom<SourceType>impls for any field namedsource, enabling?-based error conversion - Custom message blocks (
@{ ... }) for cases where simple string interpolation isn’t expressive enough - A
no_stdbuild path (via disabling the defaultstdfeature) with a bundledErrortrait forcore/allocenvironments - Passthrough support for
pubvisibility, doc comments, and derive attributes on the generated type
Common Use Cases
- Library authors who want a
thiserror-style typed error surface without adding a proc-macro dependency to their crate - CLI or application code that wraps
io::Error,ParseIntError, and similar library errors into one domain error type with automatic?conversion - Embedded or no_std projects that need a proper
Error-implementing type but can’t pull in std-only error crates - Early-stage projects iterating quickly on error variants without rewriting
impl Display/impl Errorby hand each time
Under The Hood
Architecture
custom_error is a single-macro library: the public custom_error! macro (src/lib.rs) recursively parses its token-tree input and delegates to a small set of helper macros — return_if_source!, impl_error_conversion!, impl_error_conversion_for_struct!, and display_message! — each responsible for one generated concern (source chaining, From conversions, message formatting). All codegen happens at compile time via macro_rules!; at runtime the emitted types are plain enums/structs dispatched through ordinary match and trait calls. A small src/error.rs module supplies a fallback Error trait definition (mirroring libstd’s) for no_std builds, gated behind #[cfg(not(feature = "std"))]. Because everything downstream depends on the top-level macro’s token matching, the tight coupling point is the macro’s pattern-matching grammar itself — get that wrong and every consumer’s generated type fails to compile, but the sub-macro decomposition keeps each concern isolated and testable.
Tech Stack
Pure Rust, 2018 edition, with zero runtime dependencies declared in Cargo.toml. Two Cargo features control the build: std (default, uses std::error::Error) and unstable (adds no_std impls for AllocError/TryReserveError). CI (GitHub Actions) runs cargo test against Rust 1.36.0, stable, and beta, and separately runs the test suite with --no-default-features to verify the no_std path. No build tooling beyond Cargo is used; there are no external service integrations.
Code Quality
The crate is tested through three complementary layers: an integration test file (tests/tests.rs) exercising realistic file-parsing error scenarios, an extensive inline mod tests block in lib.rs with 30+ #[test] functions covering interpolation, source chaining, struct-form errors, lifetimes, and attribute passthrough, and doctests embedded throughout the public documentation comments — meaning every documented usage example is compiled and asserted on every test run. Error handling is idiomatic Rust throughout (typed errors implementing the standard Error trait rather than strings or panics), and naming is consistent (PascalCase types, snake_case macro helpers). No explicit rustfmt.toml or clippy configuration was found in the repo, so formatting/linting isn’t enforced in CI beyond compilation and tests.
What Makes It Unique
The defining technical choice is implementing thiserror-like error-type generation entirely with macro_rules! rather than a proc-macro crate — this keeps the dependency graph empty and compile times fast, and is what makes the no_std / embedded use case practical, since proc-macro crates like syn and quote pull in a much heavier dependency chain. Automatic From<SourceType> generation for fields named source mirrors the ergonomics of thiserror’s #[from] attribute despite macro_rules!’s more limited parsing power, and the @{ ... } custom-message-block escape hatch gives an out for cases plain interpolation can’t express. It isn’t a conceptually novel idea — the ergonomic-error-macro space is well trodden — but the zero-dependency, no_std-first implementation is a distinctive constraint most alternatives don’t target.