macro_rules! StateMachineImpl {
($($input:tt)*) => { ... };
}Expand description
Connects a runtime type to a state-machine definition and generates its effects.
Invoke this macro in the implementation crate, in the same module as the
runtime type’s state-specific methods. It implements
StateMachineImpl, creates a private
transition token, and emits module-local extension traits consumed by
transition!. Keeping the token private is what
prevents callers from retagging states directly.
The macro also emits raw-state construction helpers for the implementation:
Runtime::with_state(value)is public and safe, but only works for states declaredInitialby the definition crate. TreatInitialas a public constructor contract: anyone with rawRuntimecan attach one of those states.Runtime::with_state_priv(value)is private to the invocation module and works for states listed withpriv Initial: StateName;inside this macro. Use this for target-owned conversions from another state machine.Runtime::with_state_unsafe(value)is the explicit unsafe escape hatch for arbitrary concrete states.
A private raw construction state is declared before or between transitions:
StateMachineImpl! {
Job: JobStandin;
priv Initial: Authenticated;
transition Authenticated => Authorised();
}Transition headers must match transitions declared by
StateMachineDefinition!. The
definition macro proves that an edge is legal; this macro decides what
effect runs when that edge is taken for a particular runtime type. In this
macro, headers may also carry implementation bodies:
;means the transition has no effect body. The runtime value is only retagged fromFromtoTo.{ ... }runs the body against a mutable runtime value before retagging. Inside the body,selfrefers to&mut Runtime, not to the state token.,after a header queues that header and shares the next body with it. This is not punctuation sugar. It records that those transition edges use the same effect type. Static union transitions depend on that fact:transition!(const Online self)only compiles when every concrete member uses the same body and signature for the chosen target.pinned transition From => To(...) { ... }declares an effect that runs withselfbound toPin<&mut Runtime>instead of&mut Runtime. Call it withtransition!(pin state, ...)from a method whose storage is bounded byS: SPinMut. This is the form to use when the runtime is!Unpinand the effect must call pinned methods or update fields through interior mutability without ever exposing&mut T. Pinned and ordinary effects are separate contracts, so the sameFrom => Toedge may have both a normal body and a pinned body. The call form decides which one is used.
The macro intentionally generates awkward hidden helper names such as
_magicsm_transition, _magicsm_transitionConst, _magicsm_transitionDyn,
_magicsm_transitionPin, _magicsm_transitionPinConst, and
_magicsm_transitionPinDyn. User code should call them through
transition!, which keeps transition calls
uniform and prevents method-name collisions with normal implementation
methods.
A typical implementation keeps the public methods ordinary Rust methods and
uses State<Storage, Self, StateMarker> as the receiver. The storage bound
documents what the method needs: SRef for reading, SMut for mutation and
transitions, and SMove when a backend must be movable by value.
use magicstatemachines::{transition, SMut, State, StateMachineImpl};
use test_def::{ConnectionStandin, InOnline, Online};
use test_def::states::{Authenticated, Connected, Disconnected};
pub struct Connection {
user: Option<String>,
}
StateMachineImpl! {
Connection: ConnectionStandin;
transition Disconnected => Connected();
transition Connected => Authenticated(user: String) {
self.user = Some(user);
}
transition Connected | Authenticated => Disconnected(),
transition Authenticated => Connected() {
self.user = None;
}
}
impl Connection {
pub fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
where
S: SMut,
{
transition!(self)
}
pub fn authenticate<S>(
self: State<S, Self, Connected>,
user: impl Into<String>,
) -> State<S, Self, Authenticated>
where
S: SMut,
{
transition!(self, user.into())
}
pub fn disconnect<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
where
S: SMut,
{
transition!(dyn Online self)
}
}In the example, the two => Disconnected headers share the same body, so
either transition!(const Online self) or transition!(dyn Online self)
can be used. If Connected => Disconnected and
Authenticated => Disconnected had different bodies, the static form would
stop compiling and the dynamic form would remain valid by discriminating the
concrete runtime state first.
Use the static form when the union proof is part of the API contract:
fn disconnect<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
where
S: SMut,
{
transition!(const Online self)
}Use the dynamic form when the body may differ per member, or when the value is already stored as a discriminated union state:
fn stop<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
where
S: SMut,
{
transition!(dyn Online self,)
}Pinned transitions are intentionally separate from ordinary transitions.
SPinBox<T, S> does not implement SMut for T: !Unpin, so a normal
body cannot accidentally receive &mut T. The pinned body receives
Pin<&mut T> and the method advertises that by requiring S: SPinMut:
use core::{cell::Cell, marker::PhantomPinned, pin::Pin};
use magicstatemachines::{transition, SPinMut, State};
struct Connection {
ready: Cell<bool>,
_pin: PhantomPinned,
}
impl Connection {
fn mark_ready(self: Pin<&mut Self>) {
self.as_ref().get_ref().ready.set(true);
}
}
StateMachineImpl! {
Connection: ConnectionStandin;
pinned transition Disconnected => Connected() {
self.as_mut().mark_ready();
}
}
impl Connection {
fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
where
S: SPinMut,
{
transition!(pin self)
}
}If an edge can be used from both movable and pinned storage, define both bodies. The state-machine definition still has only one edge; the implementation macro records two effects for that edge:
StateMachineImpl! {
Connection: ConnectionStandin;
transition Disconnected => Connected() {
self.connected = true;
}
pinned transition Disconnected => Connected() {
self.as_ref().connected.set(true);
}
}
impl Connection {
fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
where
S: SMut,
{
transition!(self)
}
fn connect_pinned<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
where
S: SPinMut,
{
transition!(pin self)
}
}Pinned transitions also have static and dynamic union forms. Use
transition!(pin const Online self) when every member of the union shares
the same pinned body and signature for the selected target. Use
transition!(pin dyn Online self) when the current concrete member should
be discriminated first and each member’s own pinned body should run.