Skip to main content

magicstatemachines/
contract.rs

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