Skip to main content

magicstatemachines/
contract.rs

1#[cfg(feature = "alloc")]
2use alloc::boxed::Box;
3#[cfg(feature = "alloc")]
4use core::pin::Pin;
5#[cfg(all(feature = "std", feature = "unique-rc-arc"))]
6use std::rc::UniqueRc;
7#[cfg(all(feature = "std", feature = "unique-rc-arc"))]
8use std::sync::UniqueArc;
9
10/// Declares that a definition crate permits `TState` as an initial state.
11///
12/// This trait is normally emitted by
13/// [`StateMachineDefinition!`](macro@crate::StateMachineDefinition). It is the
14/// proof used by [`State::new`](crate::State::new),
15/// [`StateOwned::new`](crate::StateOwned::new), and shared-state constructors:
16/// creating a fresh state token only compiles for states declared in the
17/// definition crate.
18///
19/// ```ignore
20/// pub struct ConnectionStandin;
21/// pub struct Disconnected;
22///
23/// impl magicstatemachines::Initial<Disconnected> for ConnectionStandin {}
24/// ```
25///
26/// Most users should not write that impl manually; prefer:
27///
28/// ```ignore
29/// magicstatemachines::StateMachineDefinition! {
30///     for ConnectionStandin;
31///
32///     pub Initial: Disconnected;
33/// }
34/// ```
35pub trait Initial<TState> {}
36
37/// Declares that a definition crate permits `TFrom -> TTo`.
38///
39/// The definition crate owns the stand-in and state types. Rust's orphan rules
40/// therefore prevent an implementation crate from adding transitions. This
41/// trait is the graph edge only; it does not define what happens to the
42/// runtime value during the edge. Runtime effects are supplied later by
43/// [`StateMachineImpl!`](macro@crate::StateMachineImpl).
44///
45/// `F` is the required positional call signature for the transition. A
46/// zero-argument transition can use the default `fn()`. A transition that must
47/// be called with a `String` declares `type F = fn(String)`.
48///
49/// ```ignore
50/// pub struct ConnectionStandin;
51/// pub struct Connected;
52/// pub struct Authenticated;
53///
54/// impl magicstatemachines::Transition<Connected, Authenticated> for ConnectionStandin {
55///     type F = fn(String);
56/// }
57/// ```
58///
59/// With the definition macro, the same declaration is usually written as:
60///
61/// ```ignore
62/// magicstatemachines::StateMachineDefinition! {
63///     for ConnectionStandin;
64///
65///     pub Initial: Connected;
66///     transition Connected => Authenticated(user: String);
67/// }
68/// ```
69///
70/// The name `user` is documentation for the contract and for the matching
71/// implementation body. The actual transition call remains positional:
72/// `transition!(self, user.into())`.
73pub trait Transition<TFrom, TTo> {
74    /// Function signature required to perform this transition.
75    type F = fn();
76}
77
78/// Stable proof that a transition signature accepts a tuple of arguments.
79#[doc(hidden)]
80pub trait TransitionSignature<Args> {}
81
82impl TransitionSignature<()> for fn() {}
83
84macro_rules! transition_signature_impls {
85    ($(($($arg:ident),+)),* $(,)?) => {
86        $(
87            impl<$($arg),+> TransitionSignature<($($arg,)+)> for fn($($arg),+) {}
88        )*
89    };
90}
91
92transition_signature_impls! {
93    (A),
94    (A, B),
95    (A, B, C),
96    (A, B, C, D),
97    (A, B, C, D, E),
98    (A, B, C, D, E, F),
99    (A, B, C, D, E, F, G),
100    (A, B, C, D, E, F, G, H),
101    (A, B, C, D, E, F, G, H, I),
102    (A, B, C, D, E, F, G, H, I, J),
103    (A, B, C, D, E, F, G, H, I, J, K),
104    (A, B, C, D, E, F, G, H, I, J, K, L),
105}
106
107/// Connects an implementation type to a state-machine definition.
108///
109/// Implementations are `'static` so storage backends can provide borrowed
110/// guard families without repeating the implementation type in the backend.
111/// The associated `Standin` selects the definition-crate contract, while
112/// `TransitionToken` is the private capability required to perform retagging.
113///
114/// [`crate::StateMachineImpl!`] generates this implementation and keeps the
115/// transition capability's construction private. Code outside the invocation
116/// module cannot manufacture the token:
117///
118/// ```compile_fail
119/// use magicstatemachines::{Initial, State, StateMachineImpl, States, StorageStateOwned, Transition};
120///
121/// mod implementation {
122///     use super::*;
123///
124///     pub struct Standin;
125///     pub struct Runtime;
126///     States! {
127///         Ready;
128///         Running;
129///     }
130///     impl Initial<Ready> for Standin {}
131///     impl Transition<Ready, Running> for Standin {}
132///
133///     magicstatemachines::StateMachineImpl!(Runtime: Standin; transition Ready => Running(););
134///
135///     pub fn ready() -> State<StorageStateOwned, Runtime, Ready> {
136///         State::new(Runtime)
137///     }
138/// }
139///
140/// let ready = implementation::ready();
141/// let _ = magicstatemachines::transition_state::<_, _, _, implementation::Running>(
142///     ready,
143///     implementation::__StateMachineTransitionToken(())
144/// ).call(());
145/// ```
146///
147/// The generated ergonomic helpers are also private to the invocation module.
148/// This means implementation methods can call [`transition!`](macro@crate::transition),
149/// but external callers can only call the methods you expose:
150///
151/// ```compile_fail
152/// use magicstatemachines::{Initial, State, States, StorageStateOwned, Transition};
153///
154/// mod implementation {
155///     use super::*;
156///
157///     pub struct Standin;
158///     pub struct Runtime;
159///     States! {
160///         Ready;
161///         Running;
162///     }
163///
164///     impl Initial<Ready> for Standin {}
165///     impl Transition<Ready, Running> for Standin {}
166///     magicstatemachines::StateMachineImpl!(Runtime: Standin; transition Ready => Running(););
167///
168///     pub fn ready() -> State<StorageStateOwned, Runtime, Ready> {
169///         State::new(Runtime)
170///     }
171/// }
172///
173/// let ready = implementation::ready();
174/// let _ = magicstatemachines::transition!(ready);
175/// ```
176pub trait StateMachineImpl: 'static {
177    /// Definition-crate ZST used to select the state-machine contract.
178    type Standin;
179
180    /// Runtime implementation controlled by the state machine.
181    type Impl: StateMachineImpl<Standin = Self::Standin, Impl = Self::Impl>;
182
183    /// Capability required to perform transitions.
184    ///
185    /// Use [`crate::StateMachineImpl!`] to generate this capability and its
186    /// private ergonomic transition helpers.
187    type TransitionToken;
188}
189
190#[cfg(feature = "alloc")]
191impl<T> StateMachineImpl for Box<T>
192where
193    T: StateMachineImpl + ?Sized,
194{
195    type Standin = T::Standin;
196    type Impl = T::Impl;
197    type TransitionToken = T::TransitionToken;
198}
199
200#[cfg(all(feature = "std", feature = "unique-rc-arc"))]
201impl<T> StateMachineImpl for UniqueRc<T>
202where
203    T: StateMachineImpl + ?Sized,
204{
205    type Standin = T::Standin;
206    type Impl = T::Impl;
207    type TransitionToken = T::TransitionToken;
208}
209
210#[cfg(all(feature = "std", feature = "unique-rc-arc"))]
211impl<T> StateMachineImpl for UniqueArc<T>
212where
213    T: StateMachineImpl + ?Sized,
214{
215    type Standin = T::Standin;
216    type Impl = T::Impl;
217    type TransitionToken = T::TransitionToken;
218}
219
220#[cfg(feature = "alloc")]
221impl<T> StateMachineImpl for Pin<Box<T>>
222where
223    T: StateMachineImpl + ?Sized,
224{
225    type Standin = T::Standin;
226    type Impl = T::Impl;
227    type TransitionToken = T::TransitionToken;
228}