Skip to main content

magicstatemachines/
state_trait.rs

1use core::any::TypeId;
2
3#[cfg(feature = "dynZST")]
4#[doc(hidden)]
5pub trait StateTraitZst: dynzst::IsZeroSized {}
6
7#[cfg(feature = "dynZST")]
8impl<T> StateTraitZst for T where T: dynzst::IsZeroSized {}
9
10#[cfg(not(feature = "dynZST"))]
11#[doc(hidden)]
12pub trait StateTraitZst {}
13
14#[cfg(not(feature = "dynZST"))]
15impl<T> StateTraitZst for T {}
16
17/// Runtime identity for a state marker after the concrete marker type has been erased.
18///
19/// Most users never implement this trait directly. State marker types should be
20/// declared with [`States!`](macro@crate::States), which wires this trait,
21/// [`StateMarker`](crate::StateMarker), concrete-state classification, and the
22/// static marker instance used by shared storage and tracing.
23///
24/// The erased marker is intentionally only an identity token. It is not the
25/// runtime data controlled by the state machine, and it is not used to make a
26/// transition valid. Valid transitions are still proven by the generic
27/// `State<Storage, T, S>` type and by the [`Transition`](crate::Transition)
28/// contract. `StateTrait` is used at runtime only where the compiler cannot
29/// keep one concrete state in the type, for example:
30///
31/// - a shared [`SRcRefCell`](crate::SRcRefCell) or
32///   [`SArcMutex`](crate::SArcMutex) stores the currently committed state next
33///   to the data so a later borrow can check `borrow::<Connected>()`;
34/// - [`TraceEntry`](crate::TraceEntry) records the source and destination state
35///   of each transition without making the trace vector generic over every
36///   state pair.
37///
38/// With the default feature set, erased states are stored as
39/// `&'static dyn StateTrait`. With the `dynZST` feature, they are stored through
40/// `dynzst::DynZSTBox<dyn StateTrait>`, and marker types must satisfy
41/// `dynzst::IsZeroSized`. The [`States!`](macro@crate::States) macro generates
42/// zero-sized marker structs, so normal users get the correct invariant without
43/// writing any unsafe code.
44///
45/// ```ignore
46/// use magicstatemachines::{StateTrait, States};
47///
48/// States! {
49///     Disconnected;
50///     Connected;
51/// }
52///
53/// let state: &'static dyn StateTrait = Disconnected::erased_state();
54/// assert!(state.type_name().ends_with("::Disconnected"));
55/// ```
56pub trait StateTrait: StateTraitZst + 'static {
57    /// Fully qualified Rust type name of the concrete state marker.
58    ///
59    /// This is meant for diagnostics and tests. Use typed APIs such as
60    /// `borrow::<Connected>()` or generated union discrimination when the
61    /// result should affect control flow.
62    fn type_name(&self) -> &'static str;
63
64    #[doc(hidden)]
65    fn type_id(&self) -> TypeId;
66
67    #[doc(hidden)]
68    fn erased_state() -> &'static dyn StateTrait
69    where
70        Self: Sized;
71}
72
73impl<T> StateTrait for T
74where
75    T: crate::StateMarker + StateTraitZst + 'static,
76{
77    fn type_name(&self) -> &'static str {
78        core::any::type_name::<T>()
79    }
80
81    fn type_id(&self) -> TypeId {
82        TypeId::of::<T>()
83    }
84
85    fn erased_state() -> &'static dyn StateTrait {
86        <T as crate::StateMarker>::erased_state()
87    }
88}
89
90#[doc(hidden)]
91pub trait ConcreteStateTrait:
92    StateTrait + crate::StateMarker<Kind = crate::ConcreteStateKind>
93{
94    fn erased_state() -> &'static dyn StateTrait
95    where
96        Self: Sized;
97}
98
99#[cfg(feature = "dynZST")]
100#[doc(hidden)]
101pub type ErasedState = dynzst::DynZSTBox<dyn StateTrait>;
102
103#[cfg(not(feature = "dynZST"))]
104#[doc(hidden)]
105pub type ErasedState = &'static dyn StateTrait;
106
107#[cfg(feature = "dynZST")]
108pub(crate) fn erased_state<T>() -> ErasedState
109where
110    T: ConcreteStateTrait,
111{
112    dynzst::DynZSTBox::with_dyn(<T as ConcreteStateTrait>::erased_state())
113}
114
115#[cfg(not(feature = "dynZST"))]
116pub(crate) fn erased_state<T>() -> ErasedState
117where
118    T: ConcreteStateTrait,
119{
120    <T as ConcreteStateTrait>::erased_state()
121}
122
123#[cfg(feature = "dynZST")]
124#[doc(hidden)]
125pub fn clone_erased(state: &ErasedState) -> ErasedState {
126    dynzst::DynZSTBox::with_dyn(&**state)
127}
128
129#[cfg(not(feature = "dynZST"))]
130#[doc(hidden)]
131pub fn clone_erased(state: &ErasedState) -> ErasedState {
132    *state
133}
134
135pub(crate) fn is_state<T>(state: &ErasedState) -> bool
136where
137    T: StateTrait,
138{
139    state.type_id() == TypeId::of::<T>()
140}
141
142pub(crate) fn static_erased_state<T>() -> ErasedState
143where
144    T: StateTrait,
145{
146    #[cfg(feature = "dynZST")]
147    {
148        dynzst::DynZSTBox::with_dyn(T::erased_state())
149    }
150    #[cfg(not(feature = "dynZST"))]
151    {
152        T::erased_state()
153    }
154}