macro_rules! StateUnion {
(
$name:ident:
$first:ident $(| $state:ident)* $(,)?
) => { ... };
(
$name:ident, enum $enum_name:ident:
$first:ident $(| $state:ident)* $(,)?
) => { ... };
(
$name:ident:
$first_super:ident $(+ $supertrait:ident)*,
$first:ident $(| $state:ident)* $(,)?
) => { ... };
(
$name:ident:
$first_super:ident $(+ $supertrait:ident)*,
enum $enum_name:ident:
$first:ident $(| $state:ident)* $(,)?
) => { ... };
(
enum $enum_name:ident:
$first:ident $(| $state:ident)* $(,)?
) => { ... };
}Expand description
Defines a named union of concrete state markers.
A state union is useful when several states support the same read-only API, or when a method may return one of several concrete states. The macro generates three public concepts:
- a marker ZST such as
Online; - a sealed membership trait such as
InOnline, implemented for the listed concrete states and for the union-erased marker itself; - a value-carrying enum such as
OnlineEnum<Storage, T>, whose variants hold the concreteState<Storage, T, Connected>/State<Storage, T, Authenticated>values; - an
In<Online>implementation for every member, which is the generic form used by helpers that take the union marker as a type parameter; - a
StateUnionDiscriminantimplementation tyingOnlinetoOnlineEnum.
The generated marker and membership trait are useful at the type level:
use magicstatemachines::{
ConcreteStateKind, In, StateMarker, StateUnion, StateUnionDiscriminant,
States, UnionStateKind,
};
States! {
Connected;
Authenticated;
}
StateUnion!(Online: Connected | Authenticated);
fn accepts_generated_trait<T: InOnline>() {}
fn accepts_generic_trait<T: In<Online>>() {}
fn assert_union_marker<T: StateMarker<Kind = UnionStateKind>>() {}
fn assert_concrete_marker<T: StateMarker<Kind = ConcreteStateKind>>() {}
accepts_generated_trait::<Connected>();
accepts_generic_trait::<Authenticated>();
assert_union_marker::<Online>();
assert_concrete_marker::<Connected>();For StateUnion!(Online: Connected | Authenticated), APIs can write
impl InOnline when they need “any online state”:
use magicstatemachines::{SRef, State};
use test_def::InOnline;
impl Connection {
fn endpoint<S>(self: &State<S, Self, impl InOnline>) -> &str
where
S: SRef,
{
&self.endpoint
}
}When runtime branching is needed, convert a concrete member into the
generated enum through EnumExt, or first convert to a
DiscriminatedState with
In::into_discriminated and then call
DiscriminatedState::discriminate:
use magicstatemachines::{DiscriminatedState, EnumExt, State};
use test_def::{Online, OnlineEnum};
fn handle_online<S>(
state: State<S, Connection, impl InOnline>,
) -> DiscriminatedState<S, Connection, Online>
where
S: magicstatemachines::SRef,
{
<_>::into_discriminated(state)
}
match handle_online(state).discriminate() {
OnlineEnum::Connected(connected) => {
// `connected` is State<S, Connection, Connected>.
}
OnlineEnum::Authenticated(authenticated) => {
// `authenticated` is State<S, Connection, Authenticated>.
}
}
// Equivalent convenience form when the marker value is in scope:
let online_enum = Online.into_enum(state);In a match, every enum variant contains a concrete state again. That is why
a branch can call methods that require Connected or Authenticated
specifically, while the enum as a whole dereferences through the union-erased
state.
A discriminated union state can also be converted back into a union-typed
state with the enum’s generated into_erased() method. That is useful when
matching temporarily recovers a concrete variant but the API should return
the broader union type again.
The supported forms are:
StateUnion!(Online: Connected | Authenticated)creates markerOnline, traitInOnline, and enumOnlineEnum.StateUnion!(Online: Parent, Connected | Authenticated)additionally makesInOnlineextendInParent.StateUnion!(Online, enum CustomOnline: Connected | Authenticated)creates markerOnline, traitInOnline, and enumCustomOnline.StateUnion!(enum OnlineEnum: Connected | Authenticated)creates only an enum. This is useful when you need a discriminating value but do not want to publish a named union marker.
Super-unions are written before the comma. In this example every Online
state is also an AllMarker state, so InOnline extends InAllMarker:
StateUnion!(AllMarker: Disconnected | Connected | Authenticated);
StateUnion!(Online: AllMarker, Connected | Authenticated);Generated membership traits are sealed and cannot be implemented downstream:
use magicstatemachines::StateUnion;
struct Connected;
struct Other;
StateUnion!(Online: Connected);
impl InOnline for Other {}A joint-state transition exists only when every member supports the same
target and function signature. StateMachineImpl!
adds one more requirement for the transition!(const ...) form: every
member must also share the same effect body. If bodies differ, use
transition!(dyn Online state) instead so the concrete variant is
discriminated before the effect is selected.
This is the type-level reason a static union transition can fail at compile time:
use magicstatemachines::{StateUnion, StateUnionState, Transition};
struct Machine;
struct Connected;
struct Authenticated;
struct Disconnected;
impl Transition<Connected, Disconnected> for Machine {}
StateUnion!(Online: Connected | Authenticated);
fn requires_disconnect<From>()
where
Machine: Transition<From, Disconnected>,
{}
requires_disconnect::<StateUnionState<Online>>();DiscriminatedState<Storage, T, Online> carries the concrete variant in
its storage. Calling discriminate() recovers the generated enum when
runtime branching is needed. The marker also names that enum through
StateUnionDiscriminant, so
<Online as StateUnionDiscriminant>::Enum<Storage, T> is
OnlineEnum<Storage, T>.