Skip to main content

magicstatemachines/macros/
states.rs

1/// Defines concrete state marker ZSTs.
2///
3/// Use this in the definition crate for every concrete state in the contract.
4/// The generated structs are markers, not runtime state. Runtime data stays in
5/// your implementation type; the marker appears only in the type of
6/// [`State`](crate::State), [`StateOwned`](crate::StateOwned), shared guards,
7/// and generated union enums.
8///
9/// Attributes placed before a state are copied to the generated struct. This
10/// is the intended way to document public state markers:
11///
12/// ```
13/// use magicstatemachines::{ConcreteStateKind, StateMarker, States};
14///
15/// States! {
16///     /// No connection has been established yet.
17///     #[derive(Debug, Default)]
18///     Disconnected;
19///
20///     /// The transport is open but no user is authenticated.
21///     Connected;
22/// }
23///
24/// fn assert_concrete_state<T>()
25/// where
26///     T: StateMarker<Kind = ConcreteStateKind>,
27/// {
28/// }
29///
30/// assert_concrete_state::<Disconnected>();
31/// let _ = format!("{:?}", Disconnected::default());
32/// ```
33///
34/// Each generated type is a public zero-sized struct implementing the traits
35/// needed by the rest of the library:
36///
37/// - [`StateMarker`](crate::StateMarker) with
38///   [`ConcreteStateKind`](crate::ConcreteStateKind), so generic code can tell
39///   concrete states apart from union markers;
40/// - [`StateTrait`](crate::StateTrait), so shared storage and tracing can keep
41///   an erased runtime marker;
42/// - the concrete-state marker trait used internally to reject storing a union
43///   as the authoritative shared state.
44///
45/// Prefer this macro over hand-written state structs. The library assumes that
46/// concrete states are ZST markers whose erased representation still names a
47/// concrete state, not a union. Shared storage uses that erased marker at the
48/// runtime boundary, tracing stores it in trace entries, and union
49/// discrimination compares against it later. Writing the structs by hand means
50/// wiring all of those invariants yourself; using `States!` keeps the state
51/// definition crate small and makes the generated rustdoc show the state docs
52/// exactly where downstream users expect them.
53#[macro_export]
54macro_rules! States {
55    ($($(#[$state_attr:meta])* $state:ident;)*) => {
56        $(
57            $(#[$state_attr])*
58            pub struct $state;
59
60            impl $crate::StateMarker for $state {
61                type Kind = $crate::ConcreteStateKind;
62
63                fn erased_state() -> &'static dyn $crate::StateTrait {
64                    <$state as $crate::ConcreteStateTrait>::erased_state()
65                }
66            }
67
68            impl $crate::ConcreteStateTrait for $state {
69                fn erased_state() -> &'static dyn $crate::StateTrait {
70                    static STATE: $state = $state;
71                    &STATE
72                }
73            }
74        )*
75    };
76}