Nestum
When nested enums are the honest model, they buy you real compile-time invariants.
If Event::Document wraps DocumentEvent, then the type system already rules out the wrong family. You cannot accidentally construct a document envelope around a user event. The problem is not correctness. The problem is that the accurate model gets noisy fast:
Construct:
Created
instead of:
Document
and match the same way with nested!.
use ;
let inner: Enum = Created;
let event: Enum = Created;
nested!
let _ = inner;
nestum keeps the nested-enum design, keeps the invariant, and removes most of the wrapping noise.
Correctness First
Use nestum when nested enums are already the right way to model the domain.
Event::Documentalways contains aDocumentEvent.Event::Useralways contains aUserEvent.- cross-family mistakes stay impossible at compile time.
nestum does not flatten the model, erase the family boundary, or swap compile-time guarantees for runtime checks. It generates nicer constructor and pattern surfaces over the same nested-enum structure.
Mental Model
#[nestum]on an enum turns the enum name into a namespace for nested-path constructors.nested! { ... }rewrites nested constructors and nested patterns where Rust syntax needs help.#[nestum_scope]rewrites a whole function, impl, method, or inline module body so you do not need as many localnested!wrappers.- If you need the concrete enum type itself, use
Outer::Enum<T>.
That namespace tradeoff is what makes Event::Document::Created possible in ordinary Rust syntax.
What This Path Means
DocumentEvent::Createdconstructs aDocumentEvent::Enum.Event::Document::Createdconstructs anEvent::Enum.Event::Documentis a namespace branch, not a completedEventvalue.
Good fits for nestum look like:
Event::Document::CreatedCommand::User::CreateMessage::Billing::Paid
Weak fits usually look like:
- a one-off wrapper where helper functions already hide the wrapping noise
- a hierarchy you would have to invent just to get prettier syntax
- names like
Document::Event::Createdthat read like inner type namespaces instead of outer envelope values
nestum is strongest when the outer enum is already a real envelope over event, command, or message families.
That usually means:
- the nested enums are already the most accurate model of the problem
- the family boundary is carrying real correctness information
- the pain is mostly constructor and match noise
Quick Start
- Add
#[nestum]to each enum in the hierarchy. - Construct wrapped values with nested paths like
Event::Document::Created. - Use
nested! { ... }for focused rewrites, or put#[nestum_scope]on the enclosing function, impl, method, or inline module to rewrite a wider scope at once.
Real-World Showcases
The nestum-examples workspace crate shows the macro against real libraries instead of toy enums.
todo_api: Axum + in-memory SQLite + broadcast events. This keeps command families, domain errors, and emitted events as separate nested enum trees instead of flattening them for convenience.ops_cli: Clap subcommands with nested dispatch. This keeps the command tree honest at the type level while still letting the dispatch code read like the tree it models.
Run them with:
Coding Agents
If you use coding agents, see docs/agents/. It includes copyable instruction templates, an opportunity-signals guide, an audit playbook, and prompts for audits, greenfield design, review, and targeted refactors.
Examples
Basic Nesting
let _ = Created;
let _ = Archived;
This is the core use case: keep the real family boundary in the type system, but stop paying for it with tuple-wrapping boilerplate.
Named-Field Constructors
Use nested! when the nested leaf is a named-field variant:
use ;
let value: Enum = nested! ;
Scope-Level Rewriting
Use #[nestum_scope] when a function or impl body has several nested constructors or patterns:
use ;
Cross-Module Nesting
Use #[nestum(external = "...")] when the inner enum lives in another module file:
let _ = A;
Ecosystem Compatibility
nestum still works with common derive-heavy Rust crates. The test suite covers:
serderound trips for wrapped outer enumsthiserrorderives with transparent outer error envelopes- transitive
#[from]conversions from leaf errors into nested outer error envelopes - common assertion macros, including
assert!(matches!(...))
Core Rules
- Only enums are supported.
- Both the outer enum and the nested inner enum need
#[nestum]. - Use
nested!for focused rewrites, or#[nestum_scope]for function-, impl-, method-, or inline-module-level rewrites.
No Type-Safety Trade
nestum is syntax and namespace machinery over real nested enums.
- It keeps the same compile-time family boundaries.
- It does not weaken the invariant that an outer branch contains the right inner enum family.
- The tradeoffs are mostly in syntax, naming, and tooling, not in static correctness.
Advanced Notes
- In type positions, use
Outer::Enum<T>for the enum type itself. - Put
#[nestum]before#[derive(...)]so derive macros see the rewritten enum shape. serde,thiserror, and common assertion macros are covered by tests.- When each nested error wrapper opts into
#[from], leaf errors can use?all the way into the outer envelope. - Generic outer enums use functions for unit constructors, so
Outer::Other()orOuter::Wrap::Ready()may be function calls instead of constants. - Plain local inner enum paths,
self::...,super::..., and qualified crate-local inner enum paths are supported, including generic arguments. - Cross-module nesting is explicit with
#[nestum(external = "crate::path::Enum")]. - For nested variants, the raw root constructor path like
Outer::Wrap(inner)is no longer part of the public surface; useOuter::Enum::Wrap(inner)if you need the explicit underlying constructor. #[nestum_scope]rewrites normal Rust AST inside the annotated item body and also handlesmatches!,assert!,debug_assert!,assert_eq!,assert_ne!, and their debug variants.
Limitations
- Most other outer macro token trees are still opaque to
#[nestum_scope]. - qself or associated paths are rejected for nested field detection.
#[path = "..."],include!(), and complexcfgmodule layouts may not resolve.- External crates are not supported because proc macros cannot reliably inspect dependency sources.
API
#[nestum]
Marks an enum so nested enum-wrapping variants can be constructed through path-shaped syntax.
use nestum;
let _ = A;
nested! { ... }
Rewrites nested constructors and nested patterns into ordinary Rust enum syntax.
Use it for match, if let, while let, let-else, matches!, common assertion macros, and named-field nested construction.
use ;
let value = B;
let ok = nested! ;
#[nestum_scope]
Rewrites nested constructors and nested patterns across a wider body.
Use it on functions, impl methods, impl blocks, or inline modules when local nested! wrappers would get noisy.
use ;
#[nestum(external = "path::to::Enum")]
Marks a variant as wrapping a nested enum defined in another module file.
use nestum;
nestum_match! { match value { ... } }
Match-only compatibility macro. Prefer nested! unless you specifically want a match-only entry point.
License
MIT