Finitomata :: FSM with rich lifecycle callbacks, persistence, and supervision

⚓ Rust    📅 2026-06-21    👤 surdeus    👁️ 1      

surdeus

A half of year ago I ported most of Erlang supervision tree model to Rust as joerl. It has been tested in the field since then and it came to port of finitomata — a Finite Automata implementation done right.

Welcome finitomata in Rust on top of joerl.

Self-explanatory quick-start from README:

use async_trait::async_trait;
use finitomata::{finitomata, Finitomata, FinitomataSupervisor, TransitionResult};
use std::time::Duration;

#[finitomata(
    fsm = r#"
        [*] --> idle
        idle --> |start| running
        running --> |stop| idle
        idle --> |shutdown| off
        off --> |confirm| [*]
    "#,
    syntax = "mermaid",
    auto_terminate = true
)]
#[derive(Debug, Clone, Default)]
struct MyFsm;

#[derive(Debug, Clone)]
struct Payload { counter: u32 }

#[async_trait]
impl Finitomata for MyFsm {
    type State = MyFsmState;
    type Event = MyFsmEvent;
    type Payload = Payload;

    async fn on_transition(
        &mut self,
        _from: &MyFsmState,
        event: &MyFsmEvent,
        _event_payload: &Payload,
        state_payload: &mut Payload,
    ) -> TransitionResult<MyFsmState, Payload> {
        match event {
            MyFsmEvent::Start => {
                state_payload.counter += 1;
                TransitionResult::Ok(MyFsmState::Running)
            }
            MyFsmEvent::Stop => TransitionResult::Ok(MyFsmState::Idle),
            MyFsmEvent::Shutdown => TransitionResult::Ok(MyFsmState::Off),
            MyFsmEvent::Confirm => TransitionResult::Ok(MyFsmState::Off),
        }
    }
}

#[tokio::main]
async fn main() {
    let graph = MyFsm::build_graph();
    let supervisor = FinitomataSupervisor::<MyFsm>::new("my_sup", graph)
        .with_auto_terminate(true);

    supervisor.start_fsm("instance_1", MyFsm, Payload { counter: 0 }).await.unwrap();

    // Send events
    supervisor.transition("instance_1", MyFsmEvent::Start, Payload { counter: 0 }).await.unwrap();
    tokio::time::sleep(Duration::from_millis(50)).await;

    // Query state (from cache, no actor round-trip)
    let state = supervisor.state("instance_1").unwrap();
    println!("Current: {:?}, Counter: {}", state.current, state.payload.counter);
}

Enjoy!

1 post - 1 participant

Read full topic

🏷️ Rust_feed