Skip to main content

StateMachineDefinition

Macro StateMachineDefinition 

Source
macro_rules! StateMachineDefinition {
    (
        for $standin:ty;
        pub Initial: $first_initial:ident $(| $initial:ident)*;
        $($transitions:tt)*
    ) => { ... };
    (
        for $standin:ty;
        Initial: $first_initial:ident $(| $initial:ident)*;
        $($transitions:tt)*
    ) => { ... };
    (
        for $standin:ty;
        $($transitions:tt)*
    ) => { ... };
}
Expand description

Defines the state-machine contract for a stand-in type.

Use this macro in the crate that owns the state marker types and the stand-in ZST. The generated code contains only contract information: Initial implementations, Transition implementations, and optional state-union declarations. It deliberately contains no runtime type and no transition bodies. Think of this as the public state-machine interface: it says which states exist, which states may be constructed initially, and which edges are legal. If no state should be constructible from raw runtime data, omit the pub Initial: line entirely; values can still enter the machine through target-owned constructors that build a ConcreteStated and pass it to State::from_concrete.

That separation is the main enforcement mechanism. A downstream crate can implement behavior for its runtime type, but it cannot add new legal transitions unless it owns the stand-in or the state markers. In the normal split-crate layout, the definition crate owns both, so Rust’s orphan rules make the transition graph a hard public contract.

The transition argument list declares the signature required by transition!. Argument names are documentation for the contract; only their types participate in the generated Transition::F signature. For example, transition Connected => Authenticated(user: String); emits an impl whose signature is equivalent to:

impl magicstatemachines::Transition<Connected, Authenticated> for ConnectionStandin {
    type F = fn(String);
}

It does not say what happens to the runtime data. The implementation crate supplies that effect later with StateMachineImpl!.

use magicstatemachines::{StateMachineDefinition, States};

pub struct ConnectionStandin;

pub mod states {
    use magicstatemachines::States;

    States! {
        Disconnected;
        Reconnecting;
        Connected;
        Authenticated;
        Failed;
    }
}

use states::*;

StateMachineDefinition! {
    for ConnectionStandin;

    pub Initial: Disconnected | Reconnecting;

    transition Disconnected => Connected | Failed();
    transition Connected => Authenticated(user: String);
    transition Connected | Authenticated => Disconnected();
    transition Authenticated => Connected();

    union All: Disconnected | Connected | Authenticated;
    union Online: All, Connected | Authenticated;
}

| on the left or right of a transition expands to every pair. For example, transition Authenticated => Connected | Failed(); declares both Authenticated -> Connected and Authenticated -> Failed with the same empty signature. transition Connected | Authenticated => Disconnected(); declares two incoming edges into Disconnected. This is only a declaration; whether the implementation shares a body is decided later by StateMachineImpl!.

Union declarations are forwarded to StateUnion!. They can be written here for convenience, but they are still independent of the stand-in and may also be written separately. A union such as union Online: Connected | Authenticated; does not add transitions by itself; it only gives APIs a way to name “any online state”.

pub Initial: is intentionally a public raw-construction capability. If a state is listed there, any code that can obtain raw implementation data can call State::new or the generated Runtime::with_state for that state. Do not list a state as public initial if entering that state must be controlled by the implementation module. For that case, omit pub Initial: and use priv Initial: StateName; inside StateMachineImpl! to create a private target-owned construction helper.

A transition declaration must end in ;. Bodies are rejected on purpose:

use magicstatemachines::{StateMachineDefinition, States};

pub struct Standin;
States! { A; B; }

StateMachineDefinition! {
    for Standin;

    pub Initial: A;

    transition A => B() {
        // Effects belong in `StateMachineImpl!`, not in the definition.
    }
}