A high-performance, memory-safe Hierarchical State Machine implementation in Rust with async/await support, leveraging Rust's type system and zero-cost abstractions for optimal performance and safety.
- π‘οΈ Memory Safe: Zero unsafe code, leveraging Rust's ownership system
- β‘ Zero-Cost Abstractions: Compile-time optimizations with no runtime overhead
- π§ Type-Safe API: Strong type guarantees and compile-time validation
- π High Performance: Optimized transition paths and lookup tables
- π Async/Await: Full tokio integration for concurrent state machines
- π― Macro-Based Builders: Ergonomic API with
define!,state!,transition!macros - π Kind System: Compile-time type hierarchy system inspired by C++ templates
- β Comprehensive Tests: 19+ test suites covering all functionality
Add this to your Cargo.toml:
[dependencies]
rust = "0.1.0"
[dev-dependencies]
tokio = { version = "1.0", features = ["full"] }use rust::*;
// Define your instance type
#[derive(Debug)]
struct MyInstance {
counter: i32,
log: Vec<String>,
}
impl Instance for MyInstance {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
#[tokio::main]
async fn main() -> Result<()> {
let instance = MyInstance {
counter: 0,
log: Vec::new()
};
let ctx = Context::new();
// Define the state machine using macros
let model = define!("SimpleMachine",
initial!(target!("idle")),
state!("idle",
entry!(|_ctx, inst: &mut MyInstance, _event| {
inst.log.push("Entered idle".to_string());
}),
transition!(on!("start"), target!("../running"))
),
state!("running",
entry!(|_ctx, inst: &mut MyInstance, _event| {
inst.log.push("Entered running".to_string());
}),
transition!(on!("stop"), target!("../idle"))
)
);
// Start the state machine
let hsm = start(&ctx, instance, model)?;
hsm.start().await;
println!("Current state: {}", hsm.state());
// Output: Current state: /SimpleMachine/idle
// Dispatch events
hsm.dispatch(&ctx, Event::new("start")).await;
println!("After start: {}", hsm.state());
// Output: After start: /SimpleMachine/running
hsm.dispatch(&ctx, Event::new("stop")).await;
println!("After stop: {}", hsm.state());
// Output: After stop: /SimpleMachine/idle
Ok(())
}// Define a simple state
state!("stateName")
// Define a state with behaviors
state!("stateName",
entry!(entry_fn),
exit!(exit_fn),
activity!(activity_fn),
transition!(on!("event"), target!("../targetState"))
)
// Final state
final_state!("done")
// Choice pseudostate
choice!("decision",
transition!(guard!(guard_fn), target!("../option1")),
transition!(target!("../option2")) // Guardless fallback
)
// Initial pseudostate
initial!(target!("defaultState"))// Event trigger
on!("eventName")
// Guard condition - SYNCHRONOUS, returns bool
guard!(|_ctx, inst: &MyInstance, _event| inst.counter > 5)
// Transition effect - SYNCHRONOUS
effect!(|_ctx, inst: &mut MyInstance, _event| {
inst.counter += 1;
})
// Target state
target!("../targetStatePath")// Entry action (executed when entering a state) - SYNCHRONOUS
entry!(|_ctx, inst: &mut MyInstance, _event| {
inst.log.push("Entered state".to_string());
})
// Exit action (executed when exiting a state) - SYNCHRONOUS
exit!(|_ctx, inst: &mut MyInstance, _event| {
inst.log.push("Exited state".to_string());
})
// Activity (runs while in state, cancelled on exit) - ASYNC
activity!(|_ctx, inst: &mut MyInstance, _event| {
inst.log.push("Activity running".to_string());
Box::pin(async move {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
})
})// One-time delay
after!(|_ctx, inst: &MyInstance, _event| {
std::time::Duration::from_secs(inst.timeout_seconds)
})
// Periodic timer
every!(|_ctx, inst: &MyInstance, _event| {
std::time::Duration::from_millis(100)
})rust/
βββ src/
β βββ lib.rs # Main library entry, re-exports
β βββ kind.rs # Macro-based type hierarchy system
β βββ path.rs # Path resolution utilities
β βββ context.rs # Execution context with cancellation
β βββ event.rs # Event system with type-safe data
β βββ element.rs # HSM element types (State, Transition, etc.)
β βββ model.rs # State machine model and lookup tables
β βββ queue.rs # Event queue management
β βββ hsm_impl.rs # HSM runtime implementation
β βββ builder.rs # Builder pattern for model construction
β βββ macro_builders.rs # Macro implementations
β βββ macros.rs # Macro definitions
β βββ error.rs # Error types
βββ examples/
β βββ kind_demo.rs # Kind system demonstration
β βββ js_compatibility_test.rs
βββ tests/
βββ basic_functionality_test.rs
βββ hierarchical_states_test.rs
βββ guard_conditions_test.rs
βββ choice_states_test.rs
βββ ... 19 total test files
Rust HSM features a unique macro-based kind system that provides compile-time type hierarchy checking, similar to C++ templates:
use rust::{make_kind, kind, is_kind};
// Create custom kinds with inheritance
let custom_kind = make_kind!(30, kind::ELEMENT);
// Check kind relationships
println!("{}", is_kind(kind::STATE_MACHINE, kind::STATE)); // true
println!("{}", is_kind(kind::CHOICE, kind::PSEUDOSTATE)); // true
println!("{}", is_kind(kind::EXTERNAL, kind::TRANSITION)); // true
// Built-in kind hierarchy:
// ELEMENT
// βββ NAMESPACE
// βββ VERTEX
// β βββ PSEUDOSTATE
// β β βββ INITIAL
// β β βββ CHOICE
// β βββ STATE
// β βββ FINAL_STATE
// βββ TRANSITION
// β βββ INTERNAL
// β βββ EXTERNAL
// β βββ LOCAL
// β βββ SELF
// βββ EVENT
// βββ COMPLETION_EVENT
// β βββ ERROR_EVENT
// βββ TIME_EVENTSee examples/kind_demo.rs for a comprehensive demonstration.
let model = define!("GameMachine",
initial!(target!("menu")),
state!("menu",
transition!(on!("start"), target!("../game"))
),
state!("game",
initial!(target!("playing")),
entry!(|_ctx, inst: &mut GameInstance, _event| {
inst.log.push("Game started".to_string());
}),
state!("playing",
transition!(on!("pause"), target!("../paused")),
transition!(on!("game_over"), target!("../../gameOver"))
),
state!("paused",
transition!(on!("resume"), target!("../playing"))
)
),
state!("gameOver",
transition!(on!("restart"), target!("../menu"))
)
);// Guard - SYNCHRONOUS, returns bool
fn can_transition(_ctx: &Context, inst: &MyInstance, _event: &Event) -> bool {
inst.counter < 10
}
// Effect - SYNCHRONOUS
fn increment_counter(_ctx: &Context, inst: &mut MyInstance, _event: &Event) {
inst.counter += 1;
}
let model = define!("ConditionalMachine",
initial!(target!("counting")),
state!("counting",
transition!(
on!("increment"),
guard!(can_transition),
effect!(increment_counter),
target!(".")
),
transition!(
on!("increment"),
target!("../maxed")
)
),
state!("maxed")
);let model = define!("DecisionMachine",
initial!(target!("input")),
state!("input",
transition!(on!("process"), target!("../decision"))
),
choice!("decision",
transition!(
guard!(|_ctx, inst: &MyInstance, _event| inst.value > 10),
target!("../high")
),
transition!(
guard!(|_ctx, inst: &MyInstance, _event| inst.value > 0),
target!("../medium")
),
transition!(target!("../low")) // Guardless fallback required
),
state!("high"),
state!("medium"),
state!("low")
);
// Validate choice states have guardless fallback
validate(&model)?;#[derive(Debug)]
struct MyData {
value: i32,
message: String,
}
// Create event with data
let event = Event::new("process").with_data(MyData {
value: 42,
message: "Hello".to_string(),
});
// Access data in handlers - SYNCHRONOUS
fn handle_event(_ctx: &Context, inst: &mut MyInstance, event: &Event) {
if let Some(data) = event.get_data::<MyData>() {
inst.log.push(format!("Received: {} - {}", data.value, data.message));
}
}let model = define!("TransitionTypesMachine",
initial!(target!("active")),
state!("active",
entry!(entry_fn),
exit!(exit_fn),
// Internal transition - no exit/entry
transition!(on!("internal"), effect!(effect_fn)),
// External transition - exit and re-enter
transition!(on!("external"), target!("."), effect!(effect_fn))
)
);
// Internal: Only effect_fn runs
// External: exit_fn β effect_fn β entry_fnlet ctx = Context::new();
// Long-running activity
activity!(|ctx, inst: &mut MyInstance, _event| {
Box::pin(async move {
loop {
if ctx.is_cancelled() {
break; // Gracefully exit
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
})
})
// Cancel all activities
ctx.cancel();Run the comprehensive test suite:
# Run all tests
cargo test
# Run specific test suite
cargo test hierarchical_states
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_basic_state_machine_with_simple_transitions- β Basic functionality (transitions, lifecycle)
- β Hierarchical states (nested states, path resolution)
- β Guard conditions (synchronous, async)
- β Choice pseudostates (with validation)
- β Entry/exit/activity actions
- β Internal vs external transitions
- β Event handling with type-safe data
- β Context and cancellation
- β Validation (choice fallbacks, final states)
- β Timer transitions (after, every)
- β Path resolution
- β Kind system hierarchy
- β Queue management
- Compile-time optimizations: Transition paths calculated at model build time
- Efficient lookups: Pre-built transition and deferred event tables
- Minimal allocations: Smart use of
Rc/Arcfor shared data - Stack allocation: Events and contexts are stack-allocated where possible
# Run benchmarks
cargo bench
# Profile with flamegraph
cargo install flamegraph
cargo flamegraph --bench hsm_bench[dependencies]
rust = { version = "0.1.0", default-features = false }
[features]
default = ["tokio"]
tokio = ["dep:tokio"] # Async/await support// Synchronous usage (no tokio)
let model = define!("SyncMachine", /* ... */);
let hsm = start(&ctx, instance, model)?;
// ... use without .awaituse rust::{Result, HsmError};
// Validation errors
match validate(&model) {
Ok(_) => println!("Model is valid"),
Err(HsmError::Validation(msg)) => eprintln!("Validation error: {}", msg),
Err(e) => eprintln!("Error: {:?}", e),
}
// Runtime errors
match hsm.dispatch(&ctx, event).await {
Ok(_) => println!("Event dispatched"),
Err(HsmError::InvalidState(msg)) => eprintln!("State error: {}", msg),
Err(e) => eprintln!("Error: {:?}", e),
}Understanding the execution model is crucial:
- Entry actions: Execute immediately when entering a state
- Exit actions: Execute immediately when exiting a state
- Guards: Evaluate immediately to determine if transition is allowed
- Effects: Execute immediately during transition
// Synchronous - no async/await needed
entry!(|_ctx, inst: &mut MyInstance, _event| {
inst.counter += 1; // Executes immediately
})
guard!(|_ctx, inst: &MyInstance, _event| {
inst.counter > 5 // Evaluates immediately, returns bool
})- Activities: Run concurrently while in a state, automatically cancelled on state exit
// Async - returns a Future that runs concurrently
activity!(|_ctx, inst: &mut MyInstance, _event| {
Box::pin(async move {
// This runs concurrently with the state machine
tokio::time::sleep(Duration::from_secs(1)).await;
// Work continues...
})
})Why this matters: Entry/exit/guards/effects are part of the transition logic and must complete immediately. Activities represent ongoing work that happens while in a state.
// β
Good - using macros
let model = define!("Machine",
initial!(target!("start")),
state!("start", transition!(on!("next"), target!("../end"))),
state!("end")
);
// β Verbose - manual builder calls
let model = define(
"Machine",
vec![
initial_with_target(target("start")),
state_with_behaviors("start", vec![
transition(vec![on("next"), target("../end")])
]),
state("end")
]
);// β
Good - specific instance type
#[derive(Debug)]
struct GameInstance {
score: i32,
level: u32,
}
impl Instance for GameInstance {
// Implementation
}
// β Bad - generic catch-all
struct Instance {
data: HashMap<String, Box<dyn Any>>,
}// β
Good - guards determine transition
choice!("check",
transition!(guard!(is_valid), target!("../success")),
transition!(target!("../failure"))
)
// β Bad - logic in effects
state!("check",
effect!(|ctx, inst, event| {
if is_valid(ctx, inst, event) {
// Manually transition... not recommended
}
Box::pin(async move {})
})
)let model = define!(/* ... */);
// β
Always validate before starting
validate(&model)?;
let hsm = start(&ctx, instance, model)?;Generate and view the full API documentation:
cargo doc --openContributions are welcome! Please ensure:
- All tests pass:
cargo test - Code is formatted:
cargo fmt - No clippy warnings:
cargo clippy - Documentation is updated
This project is licensed under the MIT License.
Part of the StateForward HSM multi-language project:
- C++: Template-based compile-time state machines
- Go: Concurrent state machines with OpenTelemetry
- JavaScript/TypeScript: High-performance web-ready implementation
- Python: asyncio-based implementation
- Zig: Systems programming state machines (WIP)
This implementation follows the HSM specification used across all StateForward implementations, ensuring consistent behavior and API design patterns across languages while leveraging Rust's unique strengths for safety and performance.
Built with π¦ Rust for memory safety and blazing fast performance!