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}