{"id":31850322,"url":"https://github.com/state-machines/state-machines-rs","last_synced_at":"2026-01-18T06:59:11.967Z","repository":{"id":318721645,"uuid":"1072539553","full_name":"state-machines/state-machines-rs","owner":"state-machines","description":"Compile-time state machine DSL for Rust, inspired by the Ruby state_machines gem.","archived":false,"fork":false,"pushed_at":"2025-10-11T22:05:12.000Z","size":152,"stargazers_count":54,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-12T06:41:39.717Z","etag":null,"topics":["embedded","no-std","procedural-macro","rust","state-machines"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/state-machines.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-08T21:33:46.000Z","updated_at":"2025-10-12T01:30:02.000Z","dependencies_parsed_at":"2025-10-12T06:41:43.131Z","dependency_job_id":"4f92c575-5d25-4848-87a5-98c3405601f2","html_url":"https://github.com/state-machines/state-machines-rs","commit_stats":null,"previous_names":["state-machines/state-machines-rs"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/state-machines/state-machines-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/state-machines%2Fstate-machines-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/state-machines%2Fstate-machines-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/state-machines%2Fstate-machines-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/state-machines%2Fstate-machines-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/state-machines","download_url":"https://codeload.github.com/state-machines/state-machines-rs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/state-machines%2Fstate-machines-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279011181,"owners_count":26084899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["embedded","no-std","procedural-macro","rust","state-machines"],"created_at":"2025-10-12T11:28:47.110Z","updated_at":"2026-01-18T06:59:11.961Z","avatar_url":"https://github.com/state-machines.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# state-machines\n\n\u003e **A learning-focused Rust port of Ruby's state_machines gem**\n\n[![Crates.io](https://img.shields.io/crates/v/state-machines.svg)](https://crates.io/crates/state-machines)\n[![Documentation](https://docs.rs/state-machines/badge.svg)](https://docs.rs/state-machines)\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE)\n[![GitHub](https://img.shields.io/badge/github-state--machines/state--machines--rs-blue)](https://github.com/state-machines/state-machines-rs)\n\n## About This Project\n\nThis is a Rust port of the popular [state_machines](https://github.com/state-machines/state_machines) Ruby gem, created as a **learning platform for Rubyists transitioning to Rust**.\n\nWhile learning Rust, I chose to port something familiar and widely used—so I could compare implementations side-by-side and understand Rust's patterns through a lens I already knew. This library is intentionally **over-commented**, not because the code is disorganized, but because it's designed to be a **teaching tool**. The goal is elegant, idiomatic Rust code that Rubyists can learn from without the usual compile-pray-repeat cycle.\n\n### Philosophy\n\n- **Learning Ground First**: Extensive inline comments explain Rust concepts, ownership, trait bounds, and macro magic\n- **Ruby Parallels**: Familiar DSL syntax and callbacks make the transition smoother\n- **Production Ready**: Despite the educational focus, this is a fully functional state machine library with:\n  - **Typestate pattern** for compile-time state safety\n  - **Zero-cost abstractions** using PhantomData\n  - Guards and unless conditions\n  - Before/after event callbacks\n  - Sync and async support\n  - `no_std` compatibility (for embedded systems)\n  - Payload support for event data\n  - Move semantics preventing invalid state transitions\n\n### For the Rust Community\n\n**You're welcome to open PRs** to fix fundamentally wrong Rust concepts—but please **don't remove comments just because \"we know it\"**. This codebase serves beginners. If something can be explained better, improve the comment. If a pattern is unidiomatic, fix it *and document why*.\n\n---\n\n## Features\n\n**Typestate Pattern** – Compile-time state safety using Rust's type system with zero runtime overhead\n\n**Guards \u0026 Unless** – Conditional transitions at event and transition levels\n\n**Callbacks** – `before`/`after` hooks at event level\n\n**Around Callbacks** – Wrap transitions with Before/AfterSuccess stages for transaction-like semantics\n\n**Async Support** – First-class `async`/`await` for guards and callbacks\n\n**Event Payloads** – Pass data through transitions with type-safe payloads\n\n**No-std Compatible** – Works on embedded targets (ESP32, bare metal)\n\n**Type-safe** – Invalid transitions become compile errors, not runtime errors\n\n**Hierarchical States** – Superstates with polymorphic transitions via SubstateOf trait\n\n**Dynamic Dispatch** – Runtime event dispatch for event-driven systems (opt-in via feature flag or explicit config)\n\n---\n\n## Quick Start\n\nAdd to your `Cargo.toml`:\n\n```toml\n[dependencies]\nstate-machines = \"0.8\"\n```\n\n### Basic Example\n\n```rust\nuse state_machines::state_machine;\n\n// Define your state machine\nstate_machine! {\n    name: TrafficLight,\n\n    initial: Red,\n    states: [Red, Yellow, Green],\n    events {\n        next {\n            transition: { from: Red, to: Green }\n            transition: { from: Green, to: Yellow }\n            transition: { from: Yellow, to: Red }\n        }\n    }\n}\n\nfn main() {\n    // Typestate pattern: each transition returns a new typed machine\n    let light = TrafficLight::new(());\n    // Type is TrafficLight\u003cRed\u003e\n\n    let light = light.next().unwrap();\n    // Type is TrafficLight\u003cGreen\u003e\n\n    let light = light.next().unwrap();\n    // Type is TrafficLight\u003cYellow\u003e\n}\n```\n\n### With Guards and Callbacks\n\n```rust\nuse state_machines::{state_machine, core::GuardError};\nuse std::sync::atomic::{AtomicBool, Ordering};\n\nstatic DOOR_OBSTRUCTED: AtomicBool = AtomicBool::new(false);\n\nstate_machine! {\n    name: Door,\n\n    initial: Closed,\n    states: [Closed, Open],\n    events {\n        open {\n            guards: [path_clear],\n            before: [check_safety],\n            after: [log_opened],\n            transition: { from: Closed, to: Open }\n        }\n        close {\n            transition: { from: Open, to: Closed }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e Door\u003cC, S\u003e {\n    fn path_clear(\u0026self, _ctx: \u0026C) -\u003e bool {\n        !DOOR_OBSTRUCTED.load(Ordering::Relaxed)\n    }\n\n    fn check_safety(\u0026self) {\n        println!(\"Checking if path is clear...\");\n    }\n\n    fn log_opened(\u0026self) {\n        println!(\"Door opened at {:?}\", std::time::SystemTime::now());\n    }\n}\n\nfn main() {\n    // Successful transition\n    let door = Door::new(());\n    let door = door.open().unwrap();\n    let door = door.close().unwrap();\n\n    // Failed guard check\n    DOOR_OBSTRUCTED.store(true, Ordering::Relaxed);\n    let err = door.open().expect_err(\"should fail when obstructed\");\n    let (_door, guard_err) = err;\n    assert_eq!(guard_err.guard, \"path_clear\");\n\n    // Inspect the error kind\n    use state_machines::core::TransitionErrorKind;\n    match guard_err.kind {\n        TransitionErrorKind::GuardFailed { guard } =\u003e {\n            println!(\"Guard '{}' failed\", guard);\n        }\n        _ =\u003e unreachable!(),\n    }\n}\n```\n\n### Concrete Context for Embedded Systems\n\nFor embedded systems or applications where the context type is known at compile time, you can specify a **concrete context type** in the macro. This allows guards and callbacks to directly access context fields without generic trait bounds.\n\n**Generic Context (Default):**\n```rust,ignore\nstate_machine! {\n    name: Door,\n    // No context specified - machine is generic over C\n}\n\nimpl\u003cC, S\u003e Door\u003cC, S\u003e {\n    fn guard(\u0026self, _ctx: \u0026C) -\u003e bool {\n        // C is generic - can't access its fields\n        false\n    }\n}\n```\n\n**Concrete Context (Embedded-Friendly):**\n```rust\nuse state_machines::state_machine;\n\n#[derive(Debug, Default)]\nstruct HardwareSensors {\n    temperature_c: i16,\n    pressure_kpa: u32,\n}\n\nstate_machine! {\n    name: Door,\n    context: HardwareSensors,  // ← Concrete context type\n    initial: Closed,\n    states: [Closed, Open],\n    events {\n        open {\n            guards: [safe_conditions],\n            transition: { from: Closed, to: Open }\n        }\n        close {\n            transition: { from: Open, to: Closed }\n        }\n    }\n}\n\nimpl\u003cS\u003e Door\u003cS\u003e {\n    fn safe_conditions(\u0026self, ctx: \u0026HardwareSensors) -\u003e bool {\n        // Direct field access!\n        ctx.temperature_c \u003e= -40\n            \u0026\u0026 ctx.temperature_c \u003c= 85\n            \u0026\u0026 ctx.pressure_kpa \u003e= 95\n            \u0026\u0026 ctx.pressure_kpa \u003c= 105\n    }\n}\n\nfn main() {\n    let sensors = HardwareSensors {\n        temperature_c: 22,\n        pressure_kpa: 101,\n    };\n\n    let door = Door::new(sensors);\n    let door = door.open().unwrap();\n    let _door = door.close().unwrap();\n}\n```\n\n**Key Differences:**\n\n| Aspect | Generic Context | Concrete Context |\n|--------|----------------|------------------|\n| **Struct signature** | `Machine\u003cC, S\u003e` | `Machine\u003cS\u003e` |\n| **Impl blocks** | `impl\u003cC, S\u003e` | `impl\u003cS\u003e` |\n| **Guard signature** | `fn(\u0026self, \u0026C)` | `fn(\u0026self, \u0026HardwareType)` |\n| **Field access** | Not possible | Direct access |\n| **Flexibility** | Works with any context | Fixed to one type |\n| **Use case** | Libraries, flexibility | Embedded, hardware |\n\n**When to Use:**\n- **Embedded systems** – Hardware types known at compile time\n- **no_std environments** – Direct hardware register access\n- **Fixed architectures** – Single deployment target\n- **Performance critical** – Compiler can optimize better\n\n**When to Avoid:**\n- **Libraries** – Users need context flexibility\n- **Multiple deployments** – Different hardware configs\n- **Generic code** – Need to work with various types\n\nSee `examples/guards_and_validation` for a complete example using concrete context for spacecraft telemetry.\n\n### Async Support\n\nThe typestate pattern works seamlessly with async Rust:\n\n```rust,ignore\nuse state_machines::state_machine;\n\nstate_machine! {\n    name: HttpRequest,\n\n    initial: Idle,\n    async: true,\n    states: [Idle, Pending, Success, Failed],\n    events {\n        send {\n            guards: [has_network],\n            transition: { from: Idle, to: Pending }\n        }\n        succeed {\n            transition: { from: Pending, to: Success }\n        }\n        fail {\n            transition: { from: Pending, to: Failed }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e HttpRequest\u003cC, S\u003e {\n    async fn has_network(\u0026self, _ctx: \u0026C) -\u003e bool {\n        // Async guard checks network availability\n        tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;\n        true\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    // Type: HttpRequest\u003cIdle\u003e\n    let request = HttpRequest::new(());\n\n    // Type: HttpRequest\u003cPending\u003e\n    let request = request.send().await.unwrap();\n\n    // Type: HttpRequest\u003cSuccess\u003e\n    let request = request.succeed().await.unwrap();\n}\n```\n\n### Event Payloads\n\n```rust\nuse state_machines::state_machine;\n\n#[derive(Clone, Debug)]\nstruct LoginCredentials {\n    username: String,\n    password: String,\n}\n\nstate_machine! {\n    name: AuthSession,\n    initial: LoggedOut,\n    states: [LoggedOut, LoggedIn, Locked],\n    events {\n        login {\n            payload: LoginCredentials,\n            guards: [valid_credentials],\n            transition: { from: LoggedOut, to: LoggedIn }\n        }\n        logout {\n            transition: { from: LoggedIn, to: LoggedOut }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e AuthSession\u003cC, S\u003e {\n    fn valid_credentials(\u0026self, _ctx: \u0026C, creds: \u0026LoginCredentials) -\u003e bool {\n        // Guard receives context and payload reference\n        creds.username == \"admin\" \u0026\u0026 creds.password == \"secret\"\n    }\n}\n\nfn main() {\n    let session = AuthSession::new(());\n    // Type is AuthSession\u003c(), LoggedOut\u003e\n\n    let good_creds = LoginCredentials {\n        username: \"admin\".to_string(),\n        password: \"secret\".to_string(),\n    };\n\n    let session = session.login(good_creds).unwrap();\n    // Type is AuthSession\u003cLoggedIn\u003e\n}\n```\n\n### Hierarchical States (Superstates)\n\nGroup related states into superstates for polymorphic transitions and cleaner state organization:\n\n```rust\nuse state_machines::state_machine;\n\n#[derive(Default, Debug, Clone)]\nstruct PrepData {\n    checklist_complete: bool,\n}\n\n#[derive(Default, Debug, Clone)]\nstruct LaunchData {\n    engines_ignited: bool,\n}\n\nstate_machine! {\n    name: LaunchSequence,\n\n    initial: Standby,\n    states: [\n        Standby,\n        superstate Flight {\n            state LaunchPrep(PrepData),\n            state Launching(LaunchData),\n        },\n        InOrbit,\n    ],\n    events {\n        enter_flight {\n            transition: { from: Standby, to: Flight }\n        }\n        ignite {\n            transition: { from: Standby, to: LaunchPrep }\n        }\n        cycle_engines {\n            transition: { from: LaunchPrep, to: Launching }\n        }\n        ascend {\n            transition: { from: Flight, to: InOrbit }\n        }\n        abort {\n            transition: { from: Flight, to: Standby }\n        }\n    }\n}\n\nfn main() {\n    // Start in Standby\n    let sequence = LaunchSequence::new(());\n\n    // Transition to Flight superstate resolves to initial child (LaunchPrep)\n    let sequence = sequence.enter_flight().unwrap();\n\n    // Access state-specific data (guaranteed non-None)\n    let prep_data = sequence.launch_prep_data();\n    println!(\"Checklist complete: {}\", prep_data.checklist_complete);\n\n    // Move to Launching within Flight superstate\n    let sequence = sequence.cycle_engines().unwrap();\n\n    // abort() is defined on Flight, but works from ANY substate\n    let sequence = sequence.abort().unwrap();\n    // Type: LaunchSequence\u003cC, Standby\u003e\n\n    // Go directly to LaunchPrep (bypassing superstate entry)\n    let sequence = sequence.ignite().unwrap();\n    // Type: LaunchSequence\u003cC, LaunchPrep\u003e\n\n    // abort() STILL works - polymorphic transition!\n    let _sequence = sequence.abort().unwrap();\n}\n```\n\n**Key Features:**\n\n- **Polymorphic Transitions**: Define transitions `from: Flight` that work from ANY substate (LaunchPrep, Launching)\n- **Automatic Resolution**: `to: Flight` transitions resolve to the superstate's initial child state\n- **State Data Storage**: Each state with data gets guaranteed accessors like `launch_prep_data()` and `launching_data()`\n- **SubstateOf Trait**: Generated trait implementations enable compile-time polymorphism\n- **Storage Lifecycle**: State data is automatically initialized on entry, cleared on exit\n\n**Under the Hood:**\n\nThe macro generates:\n\n```rust,ignore\n// Marker trait for polymorphism\nimpl SubstateOf\u003cFlight\u003e for LaunchPrep {}\nimpl SubstateOf\u003cFlight\u003e for Launching {}\n\n// Polymorphic transition implementation\nimpl\u003cC, S: SubstateOf\u003cFlight\u003e\u003e LaunchSequence\u003cC, S\u003e {\n    pub fn abort(self) -\u003e Result\u003cLaunchSequence\u003cC, Standby\u003e, ...\u003e {\n        // Works from ANY state where S implements SubstateOf\u003cFlight\u003e\n    }\n}\n\n// State-specific data accessors (no Option wrapper!)\nimpl\u003cC\u003e LaunchSequence\u003cC, LaunchPrep\u003e {\n    pub fn launch_prep_data(\u0026self) -\u003e \u0026PrepData { ... }\n    pub fn launch_prep_data_mut(\u0026mut self) -\u003e \u0026mut PrepData { ... }\n}\n```\n\n**Ruby Comparison:**\n\nRuby's `state_machines` doesn't have formal superstate support in this way. The closest equivalent would be using state predicates:\n\n```ruby\n# Ruby approach\ndef in_flight?\n  [:launch_prep, :launching].include?(state)\nend\n\n# Rust: Compile-time polymorphism via trait bounds\nimpl\u003cC, S: SubstateOf\u003cFlight\u003e\u003e LaunchSequence\u003cC, S\u003e {\n  pub fn abort(self) -\u003e ... { }\n}\n```\n\nRust's typestate pattern makes this compile-time safe with zero runtime overhead.\n\n---\n\n### Around Callbacks\n\nAround callbacks wrap transitions with **transaction-like semantics**, providing Before and AfterSuccess hooks that bracket the entire transition execution:\n\n```rust\nuse state_machines::{state_machine, core::{AroundStage, AroundOutcome}};\nuse std::sync::atomic::{AtomicUsize, Ordering};\n\nstatic CALL_COUNT: AtomicUsize = AtomicUsize::new(0);\n\nstate_machine! {\n    name: Transaction,\n    initial: Idle,\n    states: [Idle, Processing, Complete],\n    events {\n        begin {\n            around: [transaction_wrapper],\n            transition: { from: Idle, to: Processing }\n        }\n        succeed {\n            transition: { from: Processing, to: Complete }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e Transaction\u003cC, S\u003e {\n    fn transaction_wrapper(\u0026self, stage: AroundStage) -\u003e AroundOutcome\u003cIdle\u003e {\n        match stage {\n            AroundStage::Before =\u003e {\n                println!(\"Starting transaction...\");\n                CALL_COUNT.fetch_add(1, Ordering::SeqCst);\n                AroundOutcome::Proceed\n            }\n            AroundStage::AfterSuccess =\u003e {\n                println!(\"Transaction committed!\");\n                CALL_COUNT.fetch_add(10, Ordering::SeqCst);\n                AroundOutcome::Proceed\n            }\n        }\n    }\n}\n\nfn main() {\n    let transaction = Transaction::new(());\n    let transaction = transaction.begin().unwrap();\n\n    // CALL_COUNT is now 11 (Before: +1, AfterSuccess: +10)\n    assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 11);\n}\n```\n\n**Execution Order:**\n\n1. **Around Before** – Runs first, can abort the entire transition\n2. **Guards** – Event/transition guards evaluated\n3. **Before callbacks** – Event-level before hooks\n4. **State transition** – Actual state change occurs\n5. **After callbacks** – Event-level after hooks\n6. **Around AfterSuccess** – Runs last, guaranteed to execute after successful transition\n\n**Aborting Transitions:**\n\nAround callbacks at the Before stage can abort transitions by returning `AroundOutcome::Abort`:\n\n```rust\nuse state_machines::{\n    state_machine,\n    core::{AroundStage, AroundOutcome, TransitionError},\n};\n\nstate_machine! {\n    name: Guarded,\n    initial: Start,\n    states: [Start, End],\n    events {\n        advance {\n            around: [abort_guard],\n            transition: { from: Start, to: End }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e Guarded\u003cC, S\u003e {\n    fn abort_guard(\u0026self, stage: AroundStage) -\u003e AroundOutcome\u003cStart\u003e {\n        match stage {\n            AroundStage::Before =\u003e {\n                // Abort at Before stage\n                AroundOutcome::Abort(TransitionError::guard_failed(\n                    Start,\n                    \"advance\",\n                    \"abort_guard\",\n                ))\n            }\n            AroundStage::AfterSuccess =\u003e {\n                // Won't be called when Before aborts\n                AroundOutcome::Proceed\n            }\n        }\n    }\n}\n\nfn main() {\n    let machine = Guarded::new(());\n    let result = machine.advance();\n\n    assert!(result.is_err());\n    let (_machine, err) = result.unwrap_err();\n    assert_eq!(err.guard, \"abort_guard\");\n}\n```\n\n**Distinguishing Error Types:**\n\nAround callbacks preserve the full `TransitionErrorKind`, allowing you to distinguish between guard failures and action failures:\n\n```rust\nuse state_machines::{\n    state_machine,\n    core::{AroundStage, AroundOutcome, TransitionError, TransitionErrorKind},\n};\n\nstate_machine! {\n    name: Workflow,\n    initial: Pending,\n    states: [Pending, Validated, Complete],\n    events {\n        validate {\n            around: [validation_wrapper],\n            transition: { from: Pending, to: Validated }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e Workflow\u003cC, S\u003e {\n    fn validation_wrapper(\u0026self, stage: AroundStage) -\u003e AroundOutcome\u003cPending\u003e {\n        match stage {\n            AroundStage::Before =\u003e {\n                // Abort with ActionFailed (not GuardFailed)\n                AroundOutcome::Abort(TransitionError {\n                    from: Pending,\n                    event: \"validate\",\n                    kind: TransitionErrorKind::ActionFailed {\n                        action: \"validation_wrapper\",\n                    },\n                })\n            }\n            AroundStage::AfterSuccess =\u003e AroundOutcome::Proceed,\n        }\n    }\n}\n\nfn main() {\n    let workflow = Workflow::new(());\n    let result = workflow.validate();\n\n    if let Err((_workflow, err)) = result {\n        // Inspect the error kind to distinguish failure types\n        match err.kind {\n            TransitionErrorKind::GuardFailed { guard } =\u003e {\n                println!(\"Guard '{}' prevented transition\", guard);\n            }\n            TransitionErrorKind::ActionFailed { action } =\u003e {\n                println!(\"Action '{}' aborted transition\", action);\n            }\n            TransitionErrorKind::InvalidTransition =\u003e {\n                println!(\"Invalid state transition\");\n            }\n        }\n    }\n}\n```\n\n**Use Cases:**\n\n- **Database transactions** – Begin/commit semantics\n- **Resource locking** – Acquire before, release after\n- **Logging/tracing** – Instrument transitions\n- **Performance monitoring** – Measure transition duration\n- **Validation** – Pre/post-condition checks\n- **Cleanup** – Ensure resources are released after transition\n\n**Multiple Around Callbacks:**\n\nYou can specify multiple around callbacks that all execute in order:\n\n```rust,ignore\nstate_machine! {\n    name: Multi,\n    initial: X,\n    states: [X, Y],\n    events {\n        go {\n            around: [logging_wrapper, metrics_wrapper, transaction_wrapper],\n            transition: { from: X, to: Y }\n        }\n    }\n}\n```\n\nAll Before stages run in order, then the transition, then all AfterSuccess stages.\n\n**Performance:**\n\nAround callbacks achieve **zero-cost abstraction** when optimized:\n\n| Configuration | Overhead | Notes |\n|--------------|----------|-------|\n| Single around callback | ~411 ps | Same as simple transition |\n| Multiple around callbacks (3) | ~411 ps | Compiler optimizes away empty wrappers |\n| Around + guards + callbacks | ~412 ps | All features combined, negligible overhead |\n\nSee `state-machines/benches/typestate_transitions.rs` for detailed benchmarks.\n\n---\n\n## Dynamic Dispatch Mode\n\nWhile the typestate pattern provides excellent compile-time safety, sometimes you need **runtime flexibility** when events come from external sources (user input, network messages, event queues). Dynamic dispatch mode solves this by generating a runtime wrapper alongside your typestate machine.\n\n### When to Use Dynamic Mode\n\n**Use Typestate When:**\n- ✅ Control flow is known at compile time\n- ✅ Want maximum type safety\n- ✅ Performance critical (zero overhead)\n- ✅ Building DSLs or configuration pipelines\n\n**Use Dynamic When:**\n- ✅ Events from external sources (UI, network, queues)\n- ✅ Runtime event routing/dispatch\n- ✅ Need to store machines in collections\n- ✅ Building event-driven systems or GUIs\n\n**Use Both When:**\n- ✅ Type-safe setup phase, then dynamic runtime\n- ✅ Want compile-time safety where possible\n\n### Enabling Dynamic Mode\n\nDynamic dispatch is **opt-in** to keep binaries small by default. Enable it via:\n\n**Option 1: Explicit in macro (always generates dynamic code)**\n```rust,ignore\nstate_machine! {\n    name: TrafficLight,\n    dynamic: true,  // ← Enable dynamic dispatch\n    initial: Red,\n    states: [Red, Yellow, Green],\n    events { /* ... */ }\n}\n```\n\n**Option 2: Cargo feature flag (conditional compilation)**\n```toml\n[dependencies]\nstate-machines = { version = \"0.6\", features = [\"dynamic\"] }\n```\n\nWith the feature flag enabled, ALL state machines get dynamic dispatch without explicit `dynamic: true`.\n\n### Basic Dynamic Dispatch\n\n```rust,ignore\nuse state_machines::state_machine;\n\nstate_machine! {\n    name: TrafficLight,\n    dynamic: true,\n    initial: Red,\n    states: [Red, Yellow, Green],\n    events {\n        next {\n            transition: { from: Red, to: Green }\n            transition: { from: Green, to: Yellow }\n            transition: { from: Yellow, to: Red }\n        }\n    }\n}\n\nfn main() {\n    // Create dynamic machine\n    let mut light = DynamicTrafficLight::new(());\n\n    // Runtime event dispatch\n    light.handle(TrafficLightEvent::Next).unwrap();\n    assert_eq!(light.current_state(), \"Green\");\n\n    light.handle(TrafficLightEvent::Next).unwrap();\n    assert_eq!(light.current_state(), \"Yellow\");\n\n    light.handle(TrafficLightEvent::Next).unwrap();\n    assert_eq!(light.current_state(), \"Red\");\n}\n```\n\n### What Gets Generated\n\nWhen `dynamic: true` is set, the macro generates:\n\n1. **Event Enum** – Runtime representation of events\n```rust\npub enum TrafficLightEvent {\n    Next,\n    // With payloads:\n    // SetSpeed(u32),\n}\n```\n\n2. **Dynamic Machine** – Runtime dispatch wrapper\n```rust,ignore\npub struct DynamicTrafficLight\u003cC\u003e {\n    // Internal state wrapper\n}\n\nimpl\u003cC: Default\u003e DynamicTrafficLight\u003cC\u003e {\n    pub fn new(ctx: C) -\u003e Self { /* ... */ }\n    pub fn handle(\u0026mut self, event: TrafficLightEvent) -\u003e Result\u003c(), DynamicError\u003e { /* ... */ }\n    pub fn current_state(\u0026self) -\u003e \u0026'static str { /* ... */ }\n}\n```\n\n3. **Conversion Methods** – Switch between modes\n```rust,ignore\nimpl\u003cC\u003e TrafficLight\u003cC, Red\u003e {\n    pub fn into_dynamic(self) -\u003e DynamicTrafficLight\u003cC\u003e { /* ... */ }\n}\n\nimpl\u003cC\u003e DynamicTrafficLight\u003cC\u003e {\n    pub fn into_red(self) -\u003e Result\u003cTrafficLight\u003cC, Red\u003e, Self\u003e { /* ... */ }\n    pub fn into_yellow(self) -\u003e Result\u003cTrafficLight\u003cC, Yellow\u003e, Self\u003e { /* ... */ }\n    pub fn into_green(self) -\u003e Result\u003cTrafficLight\u003cC, Green\u003e, Self\u003e { /* ... */ }\n}\n```\n\n### Switching Between Modes\n\nConvert from typestate to dynamic when you need runtime flexibility:\n\n```rust,ignore\n// Start with typestate for setup\nlet light = TrafficLight::new(());\n// Type: TrafficLight\u003c(), Red\u003e\n\n// Perform type-safe transitions\nlet light = light.next().unwrap();\n// Type: TrafficLight\u003c(), Green\u003e\n\n// Convert to dynamic for event loop\nlet mut dynamic_light = light.into_dynamic();\n\n// Now handle runtime events\nloop {\n    let event = receive_event(); // From network, user input, etc\n    match dynamic_light.handle(event) {\n        Ok(()) =\u003e println!(\"Transitioned to {}\", dynamic_light.current_state()),\n        Err(e) =\u003e eprintln!(\"Transition failed: {:?}\", e),\n    }\n}\n```\n\nConvert back to typestate when you know the current state:\n\n```rust,ignore\nlet mut dynamic = DynamicTrafficLight::new(());\ndynamic.handle(TrafficLightEvent::Next).unwrap();\n\n// Extract typed machine if in Green state\nif let Ok(typed) = dynamic.into_green() {\n    // Type: TrafficLight\u003c(), Green\u003e\n    // Now have compile-time guarantees again\n    let _ = typed.next();\n}\n```\n\n### Event-Driven Example\n\nA common pattern is using dynamic mode with external event sources:\n\n```rust\nuse state_machines::{state_machine, DynamicError};\n\nstate_machine! {\n    name: Connection,\n    dynamic: true,\n    initial: Disconnected,\n    states: [Disconnected, Connecting, Connected, Failed],\n    events {\n        connect {\n            transition: { from: Disconnected, to: Connecting }\n        }\n        established {\n            transition: { from: Connecting, to: Connected }\n        }\n        timeout {\n            transition: { from: Connecting, to: Failed }\n        }\n        disconnect {\n            transition: { from: [Connecting, Connected], to: Disconnected }\n        }\n    }\n}\n\nfn handle_network_events(conn: \u0026mut DynamicConnection\u003c()\u003e) {\n    // Receive events from network layer\n    let events = vec![\n        ConnectionEvent::Connect,\n        ConnectionEvent::Established,\n        ConnectionEvent::Disconnect,\n    ];\n\n    for event in events {\n        match conn.handle(event) {\n            Ok(()) =\u003e {\n                println!(\"State: {}\", conn.current_state());\n            }\n            Err(DynamicError::InvalidTransition { from, event }) =\u003e {\n                eprintln!(\"Can't {} from {}\", event, from);\n            }\n            Err(DynamicError::GuardFailed { guard, event }) =\u003e {\n                eprintln!(\"Guard {} failed for {}\", guard, event);\n            }\n            Err(DynamicError::ActionFailed { action, event }) =\u003e {\n                eprintln!(\"Action {} failed for {}\", action, event);\n            }\n        }\n    }\n}\n\nfn main() {\n    let mut conn = DynamicConnection::new(());\n    handle_network_events(\u0026mut conn);\n}\n```\n\n### Error Handling\n\nDynamic mode provides `DynamicError` with three variants:\n\n```rust\npub enum DynamicError {\n    InvalidTransition { from: \u0026'static str, event: \u0026'static str },\n    GuardFailed { guard: \u0026'static str, event: \u0026'static str },\n    ActionFailed { action: \u0026'static str, event: \u0026'static str },\n}\n```\n\nUnlike typestate mode (which returns the old machine on error), dynamic mode keeps the machine in a valid state:\n\n```rust,ignore\nlet mut machine = DynamicTrafficLight::new(());\n\n// Invalid transition\nlet result = machine.handle(TrafficLightEvent::Next); // Red → Green (valid)\nassert!(result.is_ok());\n\n// Machine is now in Green state, regardless of success/failure\nassert_eq!(machine.current_state(), \"Green\");\n```\n\n### Performance Considerations\n\n| Mode | Overhead | Safety | Use Case |\n|------|----------|--------|----------|\n| **Typestate** | Zero (PhantomData) | Compile-time | Known sequences |\n| **Dynamic** | Enum match (~few ns) | Runtime | Event-driven |\n\nDynamic mode adds minimal runtime overhead (enum discriminant check + match). For most applications, this is negligible compared to the actual business logic.\n\n### Design Philosophy\n\nThis library provides **both** modes:\n- **Typestate by default** – Zero-cost abstractions, compile-time safety\n- **Dynamic opt-in** – Runtime flexibility when needed\n- **Seamless conversion** – Switch modes as requirements change\n\nYou're never forced to choose one over the other. Start with typestate for safety, convert to dynamic for flexibility, and back again when you need guarantees.\n\n---\n\n## Comparison to Ruby's state_machines\n\nIf you're coming from Ruby, here's how the concepts map:\n\n### Ruby\n```ruby\nclass Vehicle\n  state_machine :state, initial: :parked do\n    event :ignite do\n      transition parked: :idling\n    end\n\n    before_transition parked: :idling, do: :check_fuel\n  end\n\n  def check_fuel\n    puts \"Checking fuel...\"\n  end\nend\n\n# Usage\nvehicle = Vehicle.new\nvehicle.ignite  # Mutates vehicle in place\n```\n\n### Rust (Typestate)\n```rust\nuse state_machines::state_machine;\n\nstate_machine! {\n    name: Vehicle,\n\n    initial: Parked,\n    states: [Parked, Idling],\n    events {\n        ignite {\n            before: [check_fuel],\n            transition: { from: Parked, to: Idling }\n        }\n    }\n}\n\nimpl\u003cC, S\u003e Vehicle\u003cC, S\u003e {\n    fn check_fuel(\u0026self) {\n        println!(\"Checking fuel...\");\n    }\n}\n\nfn main() {\n    // Type: Vehicle\u003cParked\u003e\n    let vehicle = Vehicle::new(());\n\n    // Type: Vehicle\u003cIdling\u003e\n    let vehicle = vehicle.ignite().unwrap();\n}\n```\n\n**Key Differences:**\n- **Typestate pattern**: Each state is encoded in the type system (`Vehicle\u003cParked\u003e` vs `Vehicle\u003cIdling\u003e`)\n- **Move semantics**: Transitions consume the old state and return a new one\n- **Compile-time validation**: Can't call `ignite()` twice - second call won't compile!\n- **Zero overhead**: PhantomData optimizes away completely\n- **Explicit errors**: Guards return `Result\u003cMachine\u003cNewState\u003e, (Machine\u003cOldState\u003e, GuardError)\u003e`\n- **No mutation**: Callbacks take `\u0026self`, not `\u0026mut self` (machine is consumed by transition)\n\n---\n\n## `no_std` Support\n\nWorks on embedded targets like ESP32:\n\n```rust,ignore\n#![no_std]\n\nuse state_machines::state_machine;\n\nstate_machine! {\n    name: LedController,\n\n    initial: Off,\n    states: [Off, On, Blinking],\n    events {\n        toggle { transition: { from: Off, to: On } }\n        blink { transition: { from: On, to: Blinking } }\n    }\n}\n\nfn embedded_main() {\n    // Type: LedController\u003cOff\u003e\n    let led = LedController::new(());\n\n    // Type: LedController\u003cOn\u003e\n    let led = led.toggle().unwrap();\n\n    // Type: LedController\u003cBlinking\u003e\n    let led = led.blink().unwrap();\n\n    // Wire up to GPIO pins...\n}\n# fn main() {} // For doctest\n```\n\n- Disable default features: `state-machines = { version = \"0.6\", default-features = false }`\n- The library uses no allocator - purely stack-based with zero-sized state markers\n- CI runs `cargo build --no-default-features` to prevent std regressions\n- See `examples/no_std_flight/` for a complete embedded example\n\n---\n\n## Performance\n\nThis library achieves **true zero-cost abstractions** for typestate mode:\n\n| Feature | Overhead | Notes |\n|---------|----------|-------|\n| **Typestate mode** | | |\n| Guards | ~0 ps | Compiled to inline comparisons |\n| Callbacks | ~0 ps | Compiled to inline function calls |\n| Around callbacks | ~0 ps | Compiled to inline function calls |\n| Hierarchical transitions | ~3-4 ns | Minimal cost for storage lifecycle |\n| State data access | ~1 ns | Direct field access |\n| **Dynamic mode** | | |\n| Event dispatch | ~few ns | Enum match + method call |\n| State introspection | ~0 ps | Direct field access |\n\nGuards, callbacks, and around callbacks in typestate mode add **literally zero runtime overhead** - the compiler optimizes them completely. Dynamic mode adds minimal overhead (enum matching), typically under 10ns per transition.\n\nRun benchmarks yourself:\n```bash\ncargo bench --bench typestate_transitions\n```\n\n---\n\n## Documentation\n\n- **[API Docs](https://docs.rs/state-machines)** – Full API reference\n- **[Crates.io](https://crates.io/crates/state-machines)** – Published crate versions\n- **[GitHub](https://github.com/state-machines/state-machines-rs)** – Source code and issues\n\n---\n\n## Contributing\n\nContributions are welcome! This is a learning project, so:\n\n1. **Keep comments** – Explain *why*, not just *what*\n2. **Show Rust idioms** – If something is unidiomatic, fix it *and document the correct pattern*\n3. **Test thoroughly** – All tests must pass (`cargo test --workspace`)\n4. **Compare to Ruby** – If you're changing behavior, note how it differs from the Ruby gem\n\n---\n\n## License\n\nLicensed under either of:\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstate-machines%2Fstate-machines-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstate-machines%2Fstate-machines-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstate-machines%2Fstate-machines-rs/lists"}