FASM - Fallible Async State Machines
A Rust framework for building deterministic, testable, and crash-recoverable state machines with async operations and fallible state access.
Why FASM?
Traditional state machines break down in productionβrace conditions, crashes mid-operation, and bugs that only appear under load. FASM solves this by making correctness verifiable:
- π― Deterministic execution β Same inputs always produce same outputs
- π Crash recovery β Resume from any failure point automatically
- π§ͺ Simulation testing β Verify correctness across millions of operations in seconds
- π Atomicity β Transactions succeed completely or leave state unchanged
- πΎ Flexible state β In-memory, database transactions, or hybrid
Quick Example
use ;
;
How It Works
Input (user request, external data)
β
βΌ
βββββββββββββββββββββββββββββββββββββββ
β State Transition Function (STF) β
β βββββββββββββββββββββββββββββββββ β
β β’ Validates inputs β
β β’ Mutates state atomically β
β β’ Emits action descriptions β
βββββββββββββββββββββββββββββββββββββββ
β
ββββΊ State committed
β
ββββΊ Tracked Actions βββΊ External Systems βββΊ Results feed back as Input
β
ββββΊ Untracked Actions (fire-and-forget)
After crash: restore(state) β re-emit pending tracked actions
Core Concepts
State Transition Function (STF)
A deterministic function: (State, Input) β (State', Actions)
- Validates inputs and mutates state
- Emits action descriptions (not executions)
- Must be atomic: if it returns
Err, state is unchanged
Actions
Tracked Actions: Results feed back into the STF
- Payment processing, external API calls, background jobs
- Stored in state for crash recovery
- Use when the outcome affects system correctness
Untracked Actions: Fire-and-forget
- Logs, metrics, notifications, UI updates
- Not recovered after crashes
- Use when you don't need confirmation
Restore
After a crash, restore() rebuilds pending tracked actions from state:
- Pure function of state (no external queries)
- Runtime clears actions container before calling
- Enables automatic crash recovery
Atomicity
Transactional State (Database)
If state is a database transaction, atomicity is automatic:
async
In-Memory State
For in-memory state, order operations carefully:
async
Key Rules
β Must Do
- Validate before mutating β Check conditions before changing state
- Deterministic IDs β Generate from state counters, not random/time
- Store tracked actions in state β Before emitting, so restore can recreate them
- Fallible ops before mutations β For in-memory state atomicity
β Must Not Do
- No side effects in STF β No HTTP calls, no new connections
- No randomness β No
rand::random(), no unseeded RNGs - No system time β Pass timestamps via Input
- No external reads β Except through
stateparameter
Testing
Deterministic simulation testingβthe killer feature:
async
Examples
# Simple counter
# Coffee shop loyalty app with tracked/untracked actions
# Full booking system with simulation tests
When to Use FASM
β Great For
- Payment processing
- Reservation systems
- Workflow engines
- Distributed systems requiring correctness
β Overkill For
- Simple CRUD apps
- Stateless services
- Prototypes (unless correctness matters)
Version 0.3
- Simplified trait β Uses
async fndirectly (Rust 2024 edition) - Cleaner API β No more manual
Futureimplementations required - Renamed field β
Input::TrackedActionCompleted { id, result }(wasres)
Documentation
License
MIT OR Apache-2.0