Expand description
§tokio-fsm
Compile-time validated async finite state machines for Tokio.
tokio-fsm turns a standard Rust impl block into a state machine with generated state/event types, direct event application, an optional Tokio runtime adapter, lifecycle management, and compile-time graph validation. It removes transition boilerplate while keeping handler behavior explicit Rust code.
§Why tokio-fsm?
- FSM-First API: Apply events directly to an owned FSM, or use a spawned Tokio adapter when shared handles and state observation are useful.
- Async First: All handlers are native
async fnmethods. - Compile-Time Safety: Validates state reachability and transition contracts during compilation using
petgraph. - Deterministic Lifecycle: Explicit ownership model via a
Taskhandle that ensures resources are cleaned up if the caller drops the FSM.
§Quick Start
use tokio_fsm::fsm;
#[derive(Debug, Default)]
pub struct MyContext {
count: usize,
}
#[fsm(initial = Idle)]
impl MyFsm {
type Context = MyContext;
type Error = std::convert::Infallible;
#[on(state = Idle, event = Start, next = Running)]
async fn handle_start(&mut self) {
self.context.count += 1;
}
#[on(state = Running, event = Stop, next = Idle)]
async fn handle_stop(&mut self) {
}
}
#[tokio::main]
async fn main() {
// Direct ownership has no queue or background task.
let mut fsm = MyFsm::new(MyContext::default());
let state = fsm.apply(MyFsmEvent::Start).await.unwrap();
assert_eq!(state, MyFsmState::Running);
let context = fsm.into_context();
assert_eq!(context.count, 1);
// Spawning returns a Handle and a Task. The Task must be awaited or held.
let (handle, task) = MyFsm::spawn(MyContext::default());
// Apply resolves when the FSM processes the event.
let state = handle.apply(MyFsmEvent::Start).await.unwrap();
assert_eq!(state, MyFsmState::Running);
// Observer state changes
handle.wait_for_state(MyFsmState::Running).await.unwrap();
// Cooperative shutdown
handle.shutdown();
let final_context = task.await.unwrap();
assert_eq!(final_context.count, 1);
}§Generated API
For an impl named MyFsm, the macro generates:
| Type | Description |
|---|---|
MyFsm | The FSM value, with new, state, context, context_mut, into_context, and direct apply. |
MyFsmState | An enum of all discovered states. |
MyFsmEvent | An enum of all discovered events and their payloads. |
MyFsmHandle | A cloneable spawned-runtime handle for applying events and querying state. |
MyFsmTask | A Future that drives the FSM. Resolves to Result<Context, TaskError<E>>. |
§Handler Return Types
Handlers are async fn methods that define how the machine moves between states. Most handlers should declare a single next state and return:
(): A simple infallible transition to the declarednextstate.Result<(), E>: A fallible transition to the declarednextstate.Transition<MyFsmState>: An infallible dynamic transition across states listed innext = [A, B].Result<Transition<MyFsmState>, E>: Dynamic branching across states listed innext = [A, B].
§Lifecycle and Ownership
- Task Drop: If you drop the
MyFsmTaskhandle, the FSM is aborted immediately. Spawning is marked#[must_use]to prevent accidental leaks. - Handle Drop: When the last
MyFsmHandleis dropped, the internal event channel is closed. The spawned FSM exits after processing any received events. - Shutdown: Call
handle.shutdown()to cancel the FSM’s child token, thenawaitthe task to retrieve the final context. Cancellation can interrupt an in-flight handler future. - Event Application:
applyresolves after the FSM processes the event. If the processed state has no handler for that event, it returnsApplyError::Unhandled.
§Configuration and Attributes
#[fsm(initial = Idle, channel_size = 100)]: Customize the internal command queue capacity.#[on(state = Idle, event = Start, next = Running)]: Map an event in a source state to one or more target states.#[fsm(tracing = true)]: Enabletracinginstrumentation (requirestracingfeature).#[fsm(serde = true)]: Enableserdesupport for states and events (requiresserdefeature).
§Graph Validation & Safety
tokio-fsm validates your state machine at compile time to guarantee semantic correctness. If the graph violates these rules, compilation will fail:
- Initial State Validity: The declared
initialstate must exist. - Deterministic Handlers: Multiple handlers for the exact same
(State, Event)pair are rejected. - Graph Reachability: Every declared state must be reachable from the
initialstate.
Refer to the property test refer to the design here at the validator design
§Examples
For a full implementation showing Axum integration, multiple FSM instances, and error handling, see the Axum Order Processing Example.
§License
MIT
Enums§
- Apply
Error - Error returned when applying an event to an FSM.
- Task
Error - Error type returned by the FSM background task.
- Transition
- Represents a state transition in the FSM.
Attribute Macros§
- fsm
- Generates an asynchronous Finite State Machine (FSM) from an
implblock.