nestum 0.2.0

Proc-macro for nested enum paths like Enum1::Variant1::VariantA
Documentation

nestum

nestum lets nested enums read like nested paths in Rust.

Construct:

Event::Document::Created

instead of:

Event::Document(DocumentEvent::Created)

and match the same way with nested!.

use nestum::{nestum, nested};

#[nestum]
enum DocumentEvent {
    Created,
    Deleted,
}

#[nestum]
enum Event {
    Document(DocumentEvent),
}

let inner: DocumentEvent::Enum = DocumentEvent::Created;
let event: Event::Enum = Event::Document::Created;

nested! {
    match event {
        Event::Document::Created => {}
        Event::Document::Deleted => {}
    }
}

let _ = inner;

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.
  • 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::Created constructs a DocumentEvent::Enum.
  • Event::Document::Created constructs an Event::Enum.
  • Event::Document is a namespace branch, not a completed Event value.

Good fits for nestum look like:

  • Event::Document::Created
  • Command::User::Create
  • Message::Billing::Paid

Avoid shapes like Document::Event::Created as the main mental model. They read like inner type namespaces, but nestum is strongest when the outer enum is an envelope over event, command, or message families.

Quick Start

cargo add nestum
  1. Add #[nestum] to each enum in the hierarchy.
  2. Construct wrapped values with nested paths like Event::Document::Created.
  3. Wrap match, if let, while let, let-else, matches!, and named-field nested construction in nested! { ... }.

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 proves nested command trees, nested domain errors, and nested events can survive a normal web stack.
  • ops_cli: Clap subcommands with nested dispatch. This proves nestum can sit on top of derive-heavy command surfaces without turning the type tree into boilerplate.

Run them with:

cargo run -p nestum-examples --bin todo_api
cargo run -p nestum-examples --bin ops_cli -- users create dev@example.com

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

#[nestum]
enum DocumentEvent {
    Created,
    Deleted,
}

#[nestum]
enum ImageEvent {
    Uploaded,
    Archived,
}

#[nestum]
enum Event {
    Document(DocumentEvent),
    Image(ImageEvent),
}

let _ = Event::Document::Created;
let _ = Event::Image::Archived;

Named-Field Constructors

Use nested! when the nested leaf is a named-field variant:

use nestum::{nestum, nested};

#[nestum]
enum Inner {
    Struct { x: i32 },
}

#[nestum]
enum Outer {
    Wrap(Inner),
}

let value: Outer::Enum = nested! { Outer::Wrap::Struct { x: 5 } };

Cross-Module Nesting

Use #[nestum(external = "...")] when the inner enum lives in another module file:

mod inner;

#[nestum]
enum Outer {
    #[nestum(external = "crate::inner::Inner")]
    Wrap(Inner),
}

let _ = Outer::Wrap::A;

Core Rules

  • Only enums are supported.
  • Both the outer enum and the nested inner enum need #[nestum].
  • Use nested! for pattern-bearing forms and named-field nested constructors.

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.
  • Generic outer enums use functions for nested unit constructors, so Outer::Wrap::Ready() may be a function call instead of a constant.
  • Plain or qualified 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; use Outer::Enum::Wrap(inner) if you need the explicit underlying constructor.

Limitations

  • self::..., super::..., and qself or associated paths are rejected for nested field detection.
  • #[path = "..."], include!(), and complex cfg module 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::nestum;

#[nestum]
enum Inner {
    A,
    B,
}

#[nestum]
enum Outer {
    Wrap(Inner),
}

let _ = Outer::Wrap::A;

nested! { ... }

Rewrites nested constructors and nested patterns into ordinary Rust enum syntax. Use it for match, if let, while let, let-else, matches!, and named-field nested construction.

use nestum::{nestum, nested};

#[nestum]
enum Inner {
    A,
    B(u8),
}

#[nestum]
enum Outer {
    Wrap(Inner),
}

let value = Outer::Wrap::B(3);
let ok = nested! { matches!(value, Outer::Wrap::B(n) if n > 0) };

#[nestum(external = "path::to::Enum")]

Marks a variant as wrapping a nested enum defined in another module file.

use nestum::nestum;

mod inner;

#[nestum]
enum Outer {
    #[nestum(external = "crate::inner::Inner")]
    Wrap(Inner),
}

nestum_match! { match value { ... } }

Match-only compatibility macro. Prefer nested! unless you specifically want a match-only entry point.

License

MIT