State Machines in Rust
Rust's type system makes it an excellent language for implementing state machines. Enums with pattern matching provide compile-time exhaustiveness checks, and the ownership model prevents invalid state transitions at the type level. However, hand-writing complex state machines with hierarchical states, parallel regions, and history states is tedious and error-prone, even in Rust.
Existing approaches compared
| Approach | Pros | Cons |
|---|---|---|
| enum + match | Pure Rust, exhaustive checking | Manual, no hierarchy, no parallel states |
| rust-fsm | Derive macro, compile-time | Flat states only, no standard compliance |
| statig | Typestate pattern, zero-cost | No hierarchical states, limited expressiveness |
| sfsm | Procedural macro | Simple transitions only, no parallel or history |
| SCE | W3C standard, full hierarchy, AOT generated | SCXML definition required, external code generation step |
SCE takes a unique approach in the Rust ecosystem: instead of a proc macro or DSL, you define state machines in W3C SCXML (the international standard for statecharts), and SCE's code generator produces idiomatic Rust code at build time. This gives you the full power of the W3C specification -- hierarchical states, parallel regions, history, invoke, delayed events -- with type-safe generated code.
How It Works
Step 1: Define your state machine in SCXML
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
name="traffic_light" initial="red">
<state id="red">
<transition event="timer" target="green"/>
</state>
<state id="green">
<transition event="timer" target="yellow"/>
</state>
<state id="yellow">
<transition event="timer" target="red"/>
</state>
</scxml>
Step 2: Generate Rust code
# Generate Rust state machine (-l rust or --lang rust)
sce-codegen generate traffic_light.scxml -o ./src/generated/ -l rust
The generator produces a Rust module containing a State enum,
an Event enum, and a Policy struct that
implements the StatePolicy trait. The Engine<P>
type from sce-rust-runtime drives execution.
Step 3: Use in your Rust code
use sce_rust_runtime::Engine;
use generated::traffic_light::{TrafficLightPolicy, TrafficLightEvent, TrafficLightState};
fn main() {
// Create the policy (generated from SCXML) and engine
let policy = TrafficLightPolicy::new();
let mut engine = Engine::new(policy);
engine.initialize(); // Enters "red" state
engine.process_event(TrafficLightEvent::Timer); // red -> green
engine.process_event(TrafficLightEvent::Timer); // green -> yellow
engine.process_event(TrafficLightEvent::Timer); // yellow -> red
assert_eq!(engine.get_current_state(), TrafficLightState::Red);
}
process_event() is a convenience method that combines
raise_external() + step(). For fine-grained control,
use them separately:
// Low-level API: queue event, then process explicitly
engine.raise_external(TrafficLightEvent::Timer, "", "");
engine.step();
W3C SCXML Features in Rust
SCE's Rust backend passes all 202 mandatory W3C SCXML conformance tests, supporting the full specification:
| Feature | W3C Section | Status |
|---|---|---|
| Compound (hierarchical) states | 3.3 | Supported |
| Parallel states | 3.4 | Supported |
| History states (shallow + deep) | 3.10 | Supported |
| ECMAScript datamodel | B.2 | Supported |
| Invoke (parent-child) | 6.4 | Supported |
| Delayed send | 6.2 | Supported |
Why SCXML for Rust?
Using W3C SCXML instead of a Rust-specific DSL provides several advantages for Rust projects:
- Cross-language portability -- the same SCXML file generates C++, Kotlin, Rust, and Go code. Useful for projects with multi-language codebases or when migrating between languages.
- Visual tooling -- SCXML can be visualized, simulated, and validated with standard tools. SCE includes a browser-based visualizer.
- Formal specification -- W3C SCXML defines precise semantics for state entry/exit order, event priority, conflict resolution, and parallel state synchronization. No ambiguity.
- Full statechart expressiveness -- hierarchical states, parallel regions, history states, guarded transitions, and invoke are all part of the standard, not afterthought extensions.
Comparison with Rust State Machine Crates
| Feature | enum+match | rust-fsm | statig | SCE |
|---|---|---|---|---|
| Hierarchical states | Manual | No | No | Yes |
| Parallel states | Manual | No | No | Yes |
| History states | Manual | No | No | Yes |
| Guarded transitions | Manual | No | Yes | Yes |
| Delayed events | Manual | No | No | Yes |
| Standard compliance | None | None | None | W3C SCXML 1.0 |
| Cross-language | No | No | No | C++, Kotlin, Rust, Go |
| Visual tooling | No | No | No | Browser visualizer |
Getting Started
# Clone the repository
git clone --recursive https://github.com/newmassrael/scxml-core-engine.git
cd scxml-core-engine
# Generate Rust state machine from SCXML
sce-codegen generate your_machine.scxml -o ./src/generated/ -l rust
# Run the Rust W3C test suite
cargo test -p sce-rust-tests
The Rust runtime (sce-rust-runtime/) provides the
Engine<P> type and StatePolicy trait.
Test infrastructure is in sce-rust-tests/. The code generator
produces standalone Rust modules that depend only on the runtime crate.