Skip to main content

transition

Macro transition 

Source
macro_rules! transition {
    (pin const self $($rest:tt)*) => { ... };
    (pin dyn self $($rest:tt)*) => { ... };
    (const self $($rest:tt)*) => { ... };
    (dyn self $($rest:tt)*) => { ... };
    (pin const $marker:path, $state:expr) => { ... };
    (pin const $marker:path, $state:expr,) => { ... };
    (pin const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (pin dyn $marker:path, $state:expr) => { ... };
    (pin dyn $marker:path, $state:expr,) => { ... };
    (pin dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (pin const $marker:ident $state:expr) => { ... };
    (pin const $marker:ident $state:expr,) => { ... };
    (pin const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (pin dyn $marker:ident $state:expr) => { ... };
    (pin dyn $marker:ident $state:expr,) => { ... };
    (pin dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (pin $state:expr) => { ... };
    (pin $state:expr,) => { ... };
    (pin $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (const $marker:path, $state:expr) => { ... };
    (const $marker:path, $state:expr,) => { ... };
    (const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (dyn $marker:path, $state:expr) => { ... };
    (dyn $marker:path, $state:expr,) => { ... };
    (dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (const $marker:ident $state:expr) => { ... };
    (const $marker:ident $state:expr,) => { ... };
    (const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => { ... };
    (dyn $marker:ident $state:expr) => { ... };
    (dyn $marker:ident $state:expr,) => { ... };
    (dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => { ... };
    ($state:expr) => { ... };
    ($state:expr,) => { ... };
    ($state:expr, $($arg:expr),+ $(,)?) => { ... };
    (@call $call:expr) => { ... };
    (@call $call:expr, $($arg:expr),+ $(,)?) => { ... };
}
Expand description

Performs a transition inside a module that invoked StateMachineImpl!.

This is the public transition surface for implementation methods. The macro expands to private helper methods generated by StateMachineImpl!, so it can only be used from the module where that implementation macro was invoked. Callers outside the module can only use the ordinary methods you expose.

There are six call forms:

  • transition!(state) performs a concrete-state transition. The receiver state type must be a concrete marker such as Disconnected, and the target is inferred from the method’s return type. This is the simplest and cheapest form: the compiler already knows the exact source state.
  • transition!(const Online state) performs a static union transition. This is accepted only when every member of Online has the same target transition, the same argument signature, and the same implementation effect body. No runtime branch is needed for the effect; the union proof is entirely type-level.
  • transition!(dyn Online state) performs a discriminated union transition. The current concrete variant is recovered first, then that variant’s exact effect is run. Use this when the union members may have distinct bodies for the same target, or when the state is already stored as DiscriminatedState<_, _, Online>.
  • transition!(pin state) performs a concrete transition whose implementation effect was declared with pinned transition in StateMachineImpl!. The storage backend must implement SPinMut, so the effect receives Pin<&mut Runtime> rather than &mut Runtime. If the implementation declared both a normal transition From => To and a pinned transition From => To, this form selects the pinned body.
  • transition!(pin const Online state) performs the pinned version of a static union transition. It requires every member of Online to share the same pinned effect body and signature for the selected target, just as transition!(const Online state) requires a shared normal body.
  • transition!(pin dyn Online state) performs the pinned version of a discriminated union transition. It recovers the concrete variant and runs that variant’s pinned effect, so different union members may have different pinned bodies for the same target.

Arguments are positional only. The names written in StateMachineDefinition! and StateMachineImpl! document the contract, but Rust still checks only the argument order and types. Trailing commas are accepted in all forms.

The macro expands to a generated helper followed by .call((args, ...)). That keeps downstream crates away from the transition token while avoiding the unstable Fn* traits. In practice, callers should read transition!(self, user.into()) as “run the declared transition effect with this positional argument and retag the state to the return type”.

Typical implementation methods look like this:

use magicstatemachines::{transition, SMut, State};
use test_def::{InOnline, Online};
use test_def::states::{Authenticated, Connected, Disconnected};

impl Connection {
    fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
    where
        S: SMut,
    {
        transition!(self)
    }

    fn authenticate<S>(
        self: State<S, Self, Connected>,
        user: impl Into<String>,
    ) -> State<S, Self, Authenticated>
    where
        S: SMut,
    {
        transition!(self, user.into(),)
    }

    fn disconnect<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
    where
        S: SMut,
    {
        transition!(dyn Online self,)
    }
}

A trailing comma is accepted even for zero-argument union transitions. This is useful in macro-generated methods where the argument list may be empty:

transition!(self,);
transition!(const Online self,);
transition!(dyn Online self,);
transition!(pin self);
transition!(pin const Online self);
transition!(pin dyn Online self,);

The path form is available when the marker is not imported. The path form uses a comma between the marker path and the state expression so Rust can parse the macro input unambiguously:

transition!(const test_def::Online, self);
transition!(dyn test_def::Online, self, user_id);
transition!(pin const test_def::Online, self);
transition!(pin dyn test_def::Online, self, user_id);

Named-argument syntax is intentionally not part of this macro. Even if a transition is declared as transition Connected => Authenticated(user: String);, the call is still positional:

transition!(self, user);

The const and dyn forms always need the union/state marker before the receiver. Omitting it is rejected with a targeted compiler error:

struct Example;

impl Example {
    fn bad(self) {
        magicstatemachines::transition!(const self);
    }
}
struct Example;

impl Example {
    fn bad(self) {
        magicstatemachines::transition!(dyn self);
    }
}
struct Example;

impl Example {
    fn bad(self) {
        magicstatemachines::transition!(pin const self);
    }
}
struct Example;

impl Example {
    fn bad(self) {
        magicstatemachines::transition!(pin dyn self);
    }
}