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.
#[delegated_enum] and #[delegated_impl], keep reading this file.A "delegate enum" is a common pattern where an enum is created to represent possible types that implement a certain trait.
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
struct Idle { time_spent: f64, }
struct Aiming { target_id: usize, }
struct ChargingShot { progress: f64, shot_type: ShotType, }
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:
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;
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
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
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
extract_variants(derive(Serialize, Deserialize)),
impl_conversions, // <----- This setting
)]
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.
From<>/TryFrom<> implementations between your enums and their variants.SpireEnum provides three macros that work together:
#[delegated_enum] - An attribute macro for$ claude mcp add spire_enum \
-- python -m otcore.mcp_server <graph>