# stateless
A lightweight, zero-cost state machine library for Rust that separates structure from behavior.
[](https://crates.io/crates/stateless)
[](https://docs.rs/stateless)
[](https://opensource.org/licenses/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 sequential checks
- **Type safe**: Leverages Rust's type system fully
- **No dependencies**: `no_std` compatible
- **Clear code**: Business logic lives in one place, not scattered
## Installation
```toml
[dependencies]
stateless = "0.1.0"
```
## Quick Start
```rust
use stateless::statemachine;
statemachine! {
transitions: {
*Idle + Start = Running,
Running + Pause | Stop = Idle,
Idle | Running + Connect = Connected,
Connected + Disconnect = Idle,
Connected + Tick = _,
_ + Reset = Idle,
}
}
struct Machine {
state: State,
battery: u32,
ticks: u32,
}
impl Machine {
fn new() -> Self {
Self {
state: State::default(),
battery: 100,
ticks: 0,
}
}
fn start(&mut self) {
let Some(new_state) = self.state.process_event(Event::Start) else {
return;
};
if self.battery < 20 {
return;
}
self.battery -= 10;
self.state = new_state;
}
fn tick(&mut self) {
let Some(new_state) = self.state.process_event(Event::Tick) else {
return;
};
self.ticks += 1;
self.state = new_state;
}
fn reset(&mut self) {
if let Some(new_state) = self.state.process_event(Event::Reset) {
self.battery = 100;
self.ticks = 0;
self.state = new_state;
}
}
}
```
## Features
### Guards and Actions
Guards and actions live in your wrapper code, not the DSL.
`state.process_event(event)` returns `Option<State>` - the new state if the transition is valid. You check guards, perform actions, then apply the state:
```rust
fn connect(&mut self, id: u32) {
// Check if transition is valid for current state
let Some(new_state) = self.state.process_event(Event::Connect) else {
return;
};
// Guard: check preconditions
if id > self.max_connections {
return;
}
// Guard: check resources
if self.battery < 5 {
return;
}
// Actions: side effects
self.connection_id = id;
self.battery -= 5;
// Apply transition
self.state = new_state;
}
```
This approach gives you:
- Full control over when to apply transitions
- Multiple guards with early returns
- Actions only happen if all guards pass
- Zero coupling between state machine structure and business logic
- Clean, idiomatic Rust
### State Patterns
Multiple states can share transitions:
```rust
statemachine! {
transitions: {
*Ready | Waiting + Start = Active,
Active + Stop = Ready,
}
}
```
### Event Patterns
Multiple events can trigger the same transition:
```rust
statemachine! {
transitions: {
*Active + Pause | Stop = Idle,
}
}
```
### Wildcard Transitions
Transition from any state:
```rust
statemachine! {
transitions: {
*Idle + Start = Running,
_ + Reset = Idle, // From any state
}
}
```
### Internal Transitions
Stay in the same state while performing side effects:
```rust
statemachine! {
transitions: {
Moving + Tick = _, // Stays in Moving
}
}
impl Robot {
fn tick(&mut self) {
let Some(new_state) = self.state.process_event(Event::Tick) else {
return;
};
self.movement_ticks += 1; // Side effect without changing state
self.state = new_state;
}
}
```
Internal transitions are useful for periodic updates, counters, or logging while remaining in the current state.
### Custom Derives
```rust
statemachine! {
derive_states: [Debug, Clone, PartialEq, Eq, Hash],
derive_events: [Debug, Clone, PartialEq],
transitions: {
*Idle + Start = Running,
}
}
```
### Multiple State Machines
Use namespacing for multiple state machines:
```rust
statemachine! {
name: Player,
transitions: {
*Idle + Move = Walking,
}
}
statemachine! {
name: Enemy,
transitions: {
*Patrol + Spot = Chasing,
}
}
// Generates: PlayerState, PlayerEvent with PlayerState::process_event()
// Generates: EnemyState, EnemyEvent with EnemyState::process_event()
```
## DSL Syntax
```rust
statemachine! {
// Optional: namespace for multiple state machines
name: MyMachine,
// Optional: custom derives for State enum
derive_states: [Debug, Clone, PartialEq],
// Optional: custom derives for Event enum
derive_events: [Debug, Clone, PartialEq],
// Required: transition definitions
transitions: {
// Basic transition (initial state marked with *)
*Idle + Start = Running,
// State patterns (multiple source states)
Ready | Waiting + Start = Active,
// Event patterns (multiple trigger events)
Active + Stop | Pause = Idle,
// Wildcard (from any state)
_ + Reset = Idle,
// Internal transition (stay in same state)
Active + Tick = _,
}
}
```
## Generated Code
The macro generates:
```rust
// State enum
pub enum State {
Idle,
Running,
}
impl Default for State {
fn default() -> Self {
State::Idle // First state marked with *
}
}
// Event enum
pub enum Event {
Start,
Stop,
}
// Transition method on State
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
}
}
```
## Error Handling
The `process_event` method returns `Option<State>`:
- `Some(new_state)`: Transition is valid
- `None`: No valid transition for current state + event
You control when to apply the transition:
```rust
impl Machine {
fn try_transition(&mut self, event: Event) {
let Some(new_state) = self.state.process_event(event) else {
println!("Invalid transition");
return;
};
// Guards and actions here
self.state = new_state;
}
}
```
## Compile Time Validation
The macro validates your state machine at compile time.
### Duplicate Transitions
```rust
statemachine! {
transitions: {
*A + Event = B,
A + Event = C, // ERROR: duplicate transition
}
}
```
Error message:
```
error: duplicate transition: state 'A' + event 'Event' 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
```
## Performance
- **Zero cost**: Compiles to sequential `if` checks with early returns
- **No allocations**: All operations are stack based
- **Optimal codegen**: Uses `matches!()` macro for efficient pattern matching
- **No runtime overhead**: All validation happens at compile time
## FAQ
**Q: How do I write guards and actions?**
A: Call `state.process_event(event)` to get the new state if valid. Check your guards, perform actions, then apply the state. This gives you full control, zero coupling, and clean code. See the [Guards and Actions](#guards-and-actions) section.
**Q: Can I use this in `no_std` environments?**
A: Yes! The library uses `#![no_std]` and only requires `alloc` for compilation (not at runtime).
**Q: How do I handle conditional transitions?**
A: `state.process_event(event)` returns `Option<State>`. Get the new state, check your guards with early returns, perform actions, then assign `self.state = new_state`. All guards must pass before the state changes.
## Examples
See the [examples](examples/) directory for complete working examples:
- `demo.rs`: Comprehensive robot control demonstrating all DSL features including guards, actions, state patterns, internal transitions, and wildcard transitions
- `hierarchical.rs`: Hierarchical state machines using composition (player movement + weapon states)
Run examples with:
```bash
cargo run -r --example demo
cargo run -r --example hierarchical
```
## License
This project is licensed under the MIT License. See the [MIT.md](MIT.md) file for details.