nestum 0.2.4

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

Nestum

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.
  • #[nestum_scope] rewrites a whole function, impl, method, or inline module body so you do not need as many local nested! 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::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

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::Created that 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.

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. 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 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 } };

Scope-Level Rewriting

Use #[nestum_scope] when a function or impl body has several nested constructors or patterns:

use nestum::{nestum, nestum_scope};

#[nestum]
enum DocumentEvent {
    Created,
    Renamed { title: &'static str },
}

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

#[nestum_scope]
fn handle(event: Event::Enum) {
    let renamed = Event::Document::Renamed { title: "scope" };
    assert!(matches!(
        renamed,
        Event::Document::Renamed { title } if title == "scope"
    ));

    match event {
        Event::Document::Created => {}
        Event::Document::Renamed { title } => {
            let _ = title;
        }
    }
}

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;

Ecosystem Compatibility

nestum still works with common derive-heavy Rust crates. The test suite covers:

  • serde round trips for wrapped outer enums
  • thiserror derives with transparent 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.

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.
  • Generic outer enums use functions for unit constructors, so Outer::Other() or Outer::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; use Outer::Enum::Wrap(inner) if you need the explicit underlying constructor.
  • #[nestum_scope] rewrites normal Rust AST inside the annotated item body and also handles matches!, 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 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!, common assertion macros, 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_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::{nestum, nestum_scope};

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

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

#[nestum_scope]
fn demo(value: Outer::Enum) -> bool {
    let created = Outer::Wrap::B(3);
    matches!(value, Outer::Wrap::A) && matches!(created, 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