MCPcopy Index your code
hub / github.com/Houtamelo/spire_enum

github.com/Houtamelo/spire_enum @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
353 symbols 619 edges 51 files 3 documented · 1%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

SpireEnum

Note: This is crate IS no_std-compatible.

A self-proclaimed enum-macro suite for Rust, providing several macros that aim to make enums great again. (they never stopped being great, but I needed a punchline)

  • #[delegated_enum]: Placed on enums, generates a declarative macro that allows you to delegate impls for your enum in a single line. , and/or allows extracting variant types.
  • #[delegated_impl]: Placed on impl blocks, works in conjunction with #[delegated_enum] to generate your enum's delegated impls.
  • #[variant_type_table]: Place on enums, generates a table type that holds exactly one of each of the enums's variants, as well as several useful implementations for that type.
  • #[variant_generic_table]: Place on enums, works similarly to #[variant_type_table], except each value on the table is of a generic parameter instead of the variant's type.
  • #[discriminant_generic_table]: Place on enums, works similarly to #[variant_generic_table], except this is meant for enums with unit variants, accessing the values is used by indexing with the enum variant itself(instead of the variant's type).

  • For more info on the table macros, see each macro's documentation.

  • For more info on #[delegated_enum] and #[delegated_impl], keep reading this file.

Table of Contents

Showcase: What is a delegate enum?

A "delegate enum" is a common pattern where an enum is created to represent possible types that implement a certain trait.

Example

You're implementing a state machine for a turret in your video game:

```rust ignore // Trait that defines what every state should do. trait IState { fn enter(&mut self, turret_body: &mut TurretBody); fn exit(self, turret_body: &mut TurretBody);

/// Returns new state, if changed.
fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option<State>;

}

// Some state types

[derive(Serialize, Deserialize)]

struct Idle { time_spent: f64, }

[derive(Serialize, Deserialize)]

struct Aiming { target_id: usize, }

[derive(Serialize, Deserialize)]

struct ChargingShot { progress: f64, shot_type: ShotType, }

[derive(Serialize, Deserialize)]

struct CoolingDown { time_remaining: f64, }

// Their impls impl IState for Idle { / ... / } impl IState for Aiming { / ... / } impl IState for ChargingShot { / ... / } impl IState for CoolingDown { / ... / }

// Now you need some type to store which state a given Turret currently is on. // The first thing that may come to mind is to simply put it in a Box<dyn IState>, but that introduces some problems: // // - Serializing cannot be easily done by just deriving serde::Serialize, you need to resort to something like typetag. // - You lose some performance: dyn uses dynamic dispatch, and you now need to box your states when storing them. // // In this case, you know all possible variants of a state at compile time, so you could just store those in an enum:

[derive(Serialize, Deserialize)] // Serialization is very straightforward

enum State { Idle(Idle), Aiming(Aiming), ChargingShot(ChargingShot), CoolingDown(CoolingDown), }

// All good so far, but then, in your main function:

fn main() { let mut turrets = some_collection_data_structure();

while let Some(delta_time) = tick_game_internals() {
    // We want to call tick on the state of every turret.
    for Turret { body, state } in &mut turrets {
        let transition = match state {
            State::Idle(var) => var.tick(body, delta_time),
            State::Aiming(var) => var.tick(body, delta_time),
            State::ChargingShot(var) => var.tick(body, delta_time),
            State::CoolingDown(var) => var.tick(body, delta_time),
        };

        if let Some(new_state) = transition {
            // Exit old state
            let old_state = std::mem::replace(state, new_state);
            match old_state {
                State::Idle(var) => var.exit(body),
                State::Aiming(var) => var.exit(body),
                State::ChargingShot(var) => var.exit(body),
                State::CoolingDown(var) => var.exit(body),
            }

            // Enter new state
            match state {
                State::Idle(var) => var.enter(body),
                State::Aiming(var) => var.enter(body),
                State::ChargingShot(var) => var.enter(body),
                State::CoolingDown(var) => var.enter(body),
            }
        }
    }
}

}


You can imagine how verbose this gets, you need to match on all the variants, **every-single-time**, even if you want to call a method that every variant has.

`spire_enum` can help this case in multiple ways.

First, we can generate those big match statements for you, which you replace by calling the generated `delegate_state!` macro:

```rust ignore
use spire_enum::prelude::delegated_enum;

#[delegated_enum]
#[derive(Serialize, Deserialize)]
enum State {
    Idle(Idle),
    Aiming(Aiming),
    ChargingShot(ChargingShot),
    CoolingDown(CoolingDown),
}

// Your main function turns into:
fn main() {
    let mut turrets = some_collection_data_structure();

    while let Some(delta_time) = tick_game_internals() {
        // We want to call tick on the state of every turret.
        for Turret { body, state } in &mut turrets {
            // If you want to understand how the generated macro `delegate_state!` works, check the section "1.1 Basic Usage".
            let transition = delegate_state! { state.tick(body, delta_time) };

            if let Some(new_state) = transition {
                // Exit old state
                let old_state = std::mem::replace(state, new_state);
                delegate_state! { old_state.exit(body) }

                // Enter new state
                delegate_state! { state.enter(body) }
            }
        }
    }
}

// The declarative macro `delegate_state!` generates the same code mentioned in the initial example, except it doesn't pollute your view anymore :3

spire_enum can take it a step further though: instead of having you invoke that macro everytime, why not just let it implement the trait IState for your enum?

```rust ignore use spire_enum::prelude::delegate_impl;

[delegate_impl]

impl IState for State { fn enter(&mut self, turret_body: &mut TurretBody); fn exit(self, turret_body: &mut TurretBody);

/// Returns new state, if changed.
fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option<State>;

}

// Now you can just call those methods directly in the enum: fn main() { let mut turrets = some_collection_data_structure();

while let Some(delta_time) = tick_game_internals() {
    // We want to call tick on the state of every turret.
    for Turret { body, state } in &mut turrets {
        // If you want to understand how the generated macro `delegate_state!` works, check the section "1.1 Basic Usage".
        let transition = state.tick(body, delta_time);

        if let Some(new_state) = transition {
            // Exit old state
            let old_state = std::mem::replace(state, new_state);
            old_state.exit(body);

            // Enter new state
            state.enter(body);
        }
    }
}

}


But that's not where `spire_enum` stops, in this case, it can also help if you specify the setting `extract_variants` in your enum:

```rust ignore
#[delegated_enum(
    extract_variants(
        attrs(derive(Serialize, Deserialize)) // applies these attributes to every variant.
    )
)]
#[derive(Serialize, Deserialize)]
enum State {
    Idle { time_spent: f64 },
    Aiming { target_id: usize },
    ChargingShot { progress: f64, shot_type: ShotType },
    CoolingDown { time_remaining: f64 }
}

That setting will make the macro also generate and extract the types for each variant, so you don't need to declare the types anymore

This is how your entire file would look like with the usage of spire_enum

```rust ignore use spire_enum::prelude::{delegated_enum, delegate_impl};

// Trait trait IState { fn enter(&mut self, turret_body: &mut TurretBody); fn exit(self, turret_body: &mut TurretBody);

/// Returns new state, if changed.
fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option<State>;

}

// Enum

[delegated_enum(extract_variants(derive(Serialize, Deserialize)))]

[derive(Serialize, Deserialize)]

enum State { Idle { time_spent: f64 }, Aiming { target_id: usize }, ChargingShot { progress: f64, shot_type: ShotType }, CoolingDown { time_remaining: f64 } }

// Variant impls impl IState for Idle { / ... / } impl IState for Aiming { / ... / } impl IState for ChargingShot { / ... / } impl IState for CoolingDown { / ... / }

// Enum impl

[delegate_impl]

impl IState for State { fn enter(&mut self, turret_body: &mut TurretBody); fn exit(self, turret_body: &mut TurretBody); fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option; }

fn main() { let mut turrets = some_collection_data_structure();

while let Some(delta_time) = tick_game_internals() {
    for Turret { body, state } in &mut turrets {
        let transition = state.tick(body, delta_time);

        if let Some(new_state) = transition {
            let old_state = std::mem::replace(state, new_state);
            old_state.exit(body);
            state.enter(body);
        }
    }
}

}


You thought we were done? Not yet.

When implementing the trait for each variant, there's a good chance you're often converting variants from/into the enum.

```rust ignore
impl IState for Idle {
    /* other functions */

    fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option<State> {
        if let Some(target) = seek_new_target(turret_body) {
            Some(State::Aiming(Aiming { target_id: target.id }))
        } else {
            None
        }
    }
}

That's the least of our worries, but it doesn't mean we can't do better.

Let's have spire_enum generate conversions implementations too (From<Variant> for Enum, TryFrom<Enum> for Variant), which can be done with the setting impl_conversions:

```rust ignore

[delegated_enum(

extract_variants(derive(Serialize, Deserialize)),
impl_conversions, // <----- This setting

)]

[derive(Serialize, Deserialize)]

enum State { Idle { time_spent: f64 }, Aiming { target_id: usize }, ChargingShot { progress: f64, shot_type: ShotType }, CoolingDown { time_remaining: f64 } }


So now we can just use `.into()`:

```rust ignore
impl IState for Idle {
    /* other functions */

    fn tick(&mut self, turret_body: &mut TurretBody, delta_time: f64) -> Option<State> {
        if let Some(target) = seek_new_target(turret_body) {
            Some(Aiming { target_id: target.id }.into())
        } else {
            None
        }
    }
}

There's more this crate can do, but this showcase is already long enough, check the Usage section if you want to know more.

Key Features:

  • Delegation: Generates inherent/trait implementations for your enums, by delegating to their inner types.
  • Conversions: Generate From<>/TryFrom<> implementations between your enums and their variants.
  • Not limited to just plain/simple types: It properly handles generic parameters, lifetimes (bounds included), where clauses, etc.
  • Hygiene (IDE friendly!):
    • Proc macros desugar into declarative macros.
    • Token spans are preserved.
    • Macro only uses inputs feed into it, no reflection is performed, no files are read (no IO operations).
    • No state is preserved between macro invocations, each invocation is completely isolated.

Overview

SpireEnum provides three macros that work together:

  1. #[delegated_enum] - An attribute macro for

Extension points exported contracts — how you extend this code

Setting (Interface)
(no doc) [10 implementers]
tests/src/no_std_tests/settings_enum.rs
CollectIdents (Interface)
(no doc) [147 implementers]
proc_macro/src/ident_map/mod.rs
EnumExtensions (Interface)
Plug-and-Play extension methods for enums created by [`spire_enum_macros`]. There is no need to manually implement any
spire_enum/src/traits.rs
Configurable (Interface)
(no doc) [2 implementers]
tests/src/std_tests/variant_type_tables.rs
FromEnum (Interface)
(no doc)
spire_enum/src/traits.rs
Identifiable (Interface)
(no doc) [1 implementers]
tests/src/no_std_tests/variant_generic_tables.rs
FromEnumRef (Interface)
(no doc)
spire_enum/src/traits.rs
FromEnumMut (Interface)
(no doc)
spire_enum/src/traits.rs

Core symbols most depended-on inside this repo

map
called by 47
proc_macro/src/shared/optional.rs
docs_tokens
called by 26
proc_macro/src/shared/documentation.rs
len
called by 16
proc_macro/src/shared/variant.rs
insert_ty
called by 12
proc_macro/src/ident_map/mod.rs
is_some
called by 12
proc_macro/src/shared/optional.rs
as_ref
called by 11
proc_macro/src/shared/optional.rs
insert_trait
called by 10
proc_macro/src/ident_map/mod.rs
span
called by 10
proc_macro/src/delegated_enum/settings.rs

Shape

Function 146
Method 75
Class 67
Enum 57
Interface 8

Languages

Rust100%

Modules by API surface

tests/src/std_tests/conditional_compilation.rs27 symbols
proc_macro/src/ident_map/tests.rs25 symbols
tests/src/std_tests/variant_type_tables.rs24 symbols
tests/src/no_std_tests/state_machine_test.rs24 symbols
proc_macro/src/delegate_impl/trait_impl.rs18 symbols
tests/src/std_tests/variant_generic_tables.rs17 symbols
tests/src/no_std_tests/settings_enum.rs13 symbols
proc_macro/src/shared/generics.rs13 symbols
tests/src/std_tests/advanced_enum_test.rs12 symbols
proc_macro/src/delegated_enum/variant.rs11 symbols
proc_macro/src/delegated_enum/settings.rs11 symbols
proc_macro/src/delegate_impl/inherent_impl.rs11 symbols

For agents

$ claude mcp add spire_enum \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page