stateless 0.2.0

A lightweight, zero-cost state machine library that separates structure from behavior. Guards and actions live in wrapper code, not the DSL.
Documentation

stateless

A lightweight, zero-cost state machine library for Rust that separates structure from behavior.

Crates.io Documentation License: MIT

Philosophy

This library separates state machine structure from behavior. The DSL defines valid state transitions, while your wrapper code handles guards, actions, and business logic in idiomatic Rust.

Why Use This?

  • Zero coupling: State machine knows nothing about your types
  • Idiomatic Rust: Use Result, methods, and proper error handling
  • Zero cost: Compiles to efficient matches!() checks with early returns
  • Type safe: Leverages Rust's type system fully
  • No runtime dependencies: Generated code uses only core — no allocator needed, no_std compatible
  • Clear code: Business logic lives in one place, not scattered

Installation

[dependencies]
stateless = "0.2.0"

Quick Start

Define your state machine with the DSL, then use process_event to drive transitions:

use stateless::statemachine;

statemachine! {
    transitions: {
        *Idle + Start = Running,
        Running + Stop = Idle,
        _ + Reset = Idle,
    }
}

let mut state = State::default(); // Idle (marked with *)
assert_eq!(state, State::Idle);

if let Some(new_state) = state.process_event(Event::Start) {
    state = new_state;
}
assert_eq!(state, State::Running);

process_event returns Option<State>Some(new_state) if the transition is valid, None if not. This lets you insert guards and actions between checking validity and applying the transition.

Generated Code

Given this DSL:

statemachine! {
    transitions: {
        *Idle + Start = Running,
        Running + Stop = Idle,
    }
}

The macro generates:

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum State {
    Idle,
    Running,
}

impl Default for State {
    fn default() -> Self {
        State::Idle
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Event {
    Start,
    Stop,
}

impl State {
    pub fn process_event(&self, event: Event) -> Option<State> {
        // Returns Some(new_state) if transition is valid
        // Returns None if no valid transition
    }
}

Features

Guards and Actions

Guards and actions live in your wrapper code, not the DSL. Call process_event to check validity, verify your guards, perform side effects, then apply the state:

fn connect(&mut self, id: u32) {
    let Some(new_state) = self.state.process_event(Event::Connect) else {
        return;
    };

    if id > self.max_connections {
        return;
    }

    if self.battery < 5 {
        return;
    }

    self.connection_id = id;
    self.battery -= 5;
    self.state = new_state;
}

Initial State

Mark the initial state with *. This state is used for the generated Default implementation:

statemachine! {
    transitions: {
        *Idle + Start = Running,  // Idle is the initial state
        Running + Stop = Idle,
    }
}

let state = State::default(); // State::Idle

State Patterns

Multiple source states can share a transition:

statemachine! {
    transitions: {
        *Ready | Waiting + Start = Active,
        Active + Stop = Ready,
    }
}

Event Patterns

Multiple events can trigger the same transition:

statemachine! {
    transitions: {
        *Active + Pause | Stop = Idle,
    }
}

Wildcard Transitions

Transition from any state. Specific transitions always take priority over wildcards, regardless of declaration order:

statemachine! {
    transitions: {
        *Idle + Start = Running,
        _ + Reset = Idle,
    }
}

Internal Transitions

Stay in the current state while performing side effects:

statemachine! {
    transitions: {
        *Moving + Tick = _,
        Moving + Arrive = Idle,
    }
}

impl Robot {
    fn tick(&mut self) {
        let Some(new_state) = self.state.process_event(Event::Tick) else {
            return;
        };

        self.movement_ticks += 1;
        self.state = new_state;
    }
}

Custom Derives

Default derives are Debug, Copy, Clone, PartialEq, Eq. Override with derive_states and derive_events:

statemachine! {
    derive_states: [Debug, Clone, PartialEq, Eq, Hash],
    derive_events: [Debug, Clone, PartialEq],
    transitions: {
        *Idle + Start = Running,
    }
}

Multiple State Machines

Use name for namespacing when you need multiple state machines in the same scope:

statemachine! {
    name: Player,
    transitions: {
        *Idle + Move = Walking,
    }
}

statemachine! {
    name: Enemy,
    transitions: {
        *Patrol + Spot = Chasing,
    }
}

// Generates: PlayerState, PlayerEvent, EnemyState, EnemyEvent

Compile Time Validation

The macro validates your state machine at compile time. Duplicate transitions are rejected:

statemachine! {
    transitions: {
        *A + Go = B,
        A + Go = C,  // ERROR: duplicate transition
    }
}
error: duplicate transition: state 'A' + event 'Go' is already defined
       help: each combination of source state and event can only appear once
       note: if you need conditional behavior, use different events or handle logic in your wrapper

DSL Reference

statemachine! {
    name: MyMachine,                          // Optional: generates MyMachineState, MyMachineEvent
    derive_states: [Debug, Clone, PartialEq], // Optional: custom derives for State enum
    derive_events: [Debug, Clone, PartialEq], // Optional: custom derives for Event enum

    transitions: {
        *Idle + Start = Running,              // Initial state marked with *
        Ready | Waiting + Start = Active,     // State patterns (multiple source states)
        Active + Stop | Pause = Idle,         // Event patterns (multiple trigger events)
        _ + Reset = Idle,                     // Wildcard (from any state, lowest priority)
        Active + Tick = _,                    // Internal transition (stay in same state)
    }
}

FAQ

Q: Can I use this in no_std environments?

A: Yes. The generated code uses only core types (Option, Default) and requires no allocator at runtime.

Examples

See the examples directory for complete working examples:

  • demo.rs: Robot control demonstrating guards, actions, state patterns, internal transitions, and wildcards
  • hierarchical.rs: Hierarchical state machines using composition (player movement + weapon states)
cargo run -r --example demo
cargo run -r --example hierarchical

License

This project is licensed under the MIT License. See the MIT.md file for details.