magicstatemachines/shared/mod.rs
1mod guard;
2mod storage;
3mod weak;
4
5use crate::{
6 Initial, RuntimeStateMarker, SOwned, State, StateMachineImpl, StateMarker,
7 StateRuntimeMarkerFor, StateTrait, state_trait,
8};
9use core::marker::PhantomData;
10use std::rc::Rc;
11use std::sync::Arc;
12
13pub use guard::{
14 SharedBorrowState, StateMut, StateMutTransitionCall, StateRef, StorageStateMut,
15 StorageStateRef, transition_mut,
16};
17pub use storage::{
18 MutexStorage, RefCellStorage, RwLockStorage, SharedStateError, SharedStorage, SharedValue,
19 WrongStateError,
20};
21pub use weak::{WeakSArc, WeakSArcMutex, WeakSArcRwLock, WeakSRc, WeakSRcRefCell};
22
23/// Shared state using an explicit, replaceable storage backend.
24///
25/// `SharedState` is the runtime boundary for this library. Owned state tokens
26/// carry their current state only in the type system. Shared containers such
27/// as `Rc<RefCell<_>>`, `Arc<Mutex<_>>`, and `Arc<RwLock<_>>` need one
28/// authoritative runtime marker because aliases can request typed views at
29/// different times.
30///
31/// A borrow checks that runtime marker first. After the check succeeds, the
32/// returned value is again a statically typed `State` view, so ordinary
33/// read-only state-machine methods regain compile-time guarantees:
34///
35/// ```ignore
36/// use magicstatemachines::{SArcMutex, transition};
37/// use test_def::{Online, states::{Connected, Disconnected}};
38///
39/// let shared = SArcMutex::<Connection>::new::<Disconnected>(
40/// Connection::new("localhost:8080"),
41/// );
42///
43/// {
44/// let disconnected = shared.borrow_mut::<Disconnected>()?;
45/// let connected = transition!(disconnected);
46/// drop(connected); // commits `Connected` back to the shared container.
47/// }
48///
49/// let connected = shared.borrow::<Connected>()?;
50/// let online = shared.borrow::<Online>()?;
51/// ```
52///
53/// The storage backend is an explicit type parameter. The built-in aliases
54/// cover the common cases:
55///
56/// - [`SRcRefCell<T>`] for single-threaded shared mutable state;
57/// - [`SArcMutex<T>`] for shared state protected by `std::sync::Mutex`;
58/// - [`SArcRwLock<T>`] for shared state protected by `std::sync::RwLock`;
59/// - [`SRc<Storage, T>`] and [`SArc<Storage, T>`] when you provide a custom
60/// [`SharedStorage`] implementation.
61///
62/// Union markers can be borrowed, but cannot be stored as the committed runtime state:
63///
64/// ```compile_fail
65/// use magicstatemachines::{SArcMutex, StateMachineDefinition, StateMachineImpl, States};
66///
67/// struct Machine;
68/// struct Standin;
69///
70/// States! {
71/// A;
72/// B;
73/// }
74///
75/// StateMachineDefinition! {
76/// for Standin;
77///
78/// Initial: A;
79/// transition A => B();
80/// union Any: A | B;
81/// }
82///
83/// StateMachineImpl! {
84/// Machine: Standin;
85///
86/// transition A => B();
87/// }
88///
89/// let _state = SArcMutex::<Machine>::new::<Any>(Machine);
90/// ```
91pub struct SharedState<P, S, T>
92where
93 S: SharedStorage,
94{
95 pub(super) storage: P,
96 pub(super) backend: PhantomData<fn() -> S>,
97 pub(super) value: PhantomData<fn() -> T>,
98}
99
100impl<P: Clone, S: SharedStorage, T> Clone for SharedState<P, S, T> {
101 fn clone(&self) -> Self {
102 Self {
103 storage: self.storage.clone(),
104 backend: PhantomData,
105 value: PhantomData,
106 }
107 }
108}
109
110impl<P, Backend, T> SharedState<P, Backend, T>
111where
112 Backend: SharedStorage,
113 P: From<Backend::Storage<T>> + AsRef<Backend::Storage<T>>,
114 T: StateMachineImpl,
115{
116 /// Creates shared state from a runtime value in an allowed initial state.
117 ///
118 /// `State` must be a concrete initial state declared by the definition
119 /// crate. Union markers are intentionally rejected as committed storage
120 /// states; they can be borrowed as views after a concrete state is stored.
121 ///
122 /// ```ignore
123 /// let shared = SArcMutex::<Connection>::new::<Disconnected>(
124 /// Connection::new("localhost:8080"),
125 /// );
126 /// ```
127 #[must_use]
128 pub fn new<State>(value: T) -> Self
129 where
130 T::Standin: Initial<State>,
131 State: crate::ConcreteStateTrait,
132 {
133 Self {
134 storage: P::from(Backend::new(SharedValue {
135 state: state_trait::erased_state::<State>(),
136 value,
137 })),
138 backend: PhantomData,
139 value: PhantomData,
140 }
141 }
142
143 /// Moves an owned state token into shared storage without changing state.
144 ///
145 /// This is the shared-storage counterpart to putting an already-created
146 /// [`State`] into `Rc`, `Arc`, or another container. The committed runtime
147 /// marker is taken from the concrete state token.
148 ///
149 /// ```ignore
150 /// let disconnected: State<SOwned, Connection, Disconnected> =
151 /// State::new(Connection::new("localhost:8080"));
152 /// let shared = SArcMutex::<Connection>::from_state(disconnected);
153 /// ```
154 #[must_use]
155 pub fn from_state<StateMarker>(state: State<SOwned, T, StateMarker>) -> Self
156 where
157 StateMarker: crate::ConcreteStateTrait,
158 {
159 Self {
160 storage: P::from(Backend::new(SharedValue {
161 state: state_trait::erased_state::<StateMarker>(),
162 value: state.inner.value,
163 })),
164 backend: PhantomData,
165 value: PhantomData,
166 }
167 }
168
169 /// Borrows the runtime value if the committed state matches `RequestedState`.
170 ///
171 /// `RequestedState` may be a concrete state or a generated union marker.
172 /// Concrete borrows require the exact committed state. Union borrows
173 /// succeed when the committed concrete state is a member of that union.
174 /// Errors distinguish "the container could not be borrowed/locked" from
175 /// "the borrow succeeded but the state was wrong":
176 ///
177 /// ```ignore
178 /// let connected = shared.borrow::<Connected>()?;
179 /// let online = shared.borrow::<Online>()?;
180 ///
181 /// match shared.borrow::<Authenticated>() {
182 /// Err(magicstatemachines::SharedStateError::WrongState(error)) => {
183 /// eprintln!("{error}");
184 /// }
185 /// other => { /* storage errors and success are handled separately */ }
186 /// }
187 /// ```
188 pub fn borrow<RequestedState>(
189 &self,
190 ) -> Result<
191 SRefView<'_, Backend, T, RuntimeStateMarker<RequestedState>>,
192 SharedStateError<Backend::ReadError<'_, T>>,
193 >
194 where
195 RequestedState:
196 StateTrait + StateMarker + StateRuntimeMarkerFor<<RequestedState as StateMarker>::Kind>,
197 RuntimeStateMarker<RequestedState>: SharedBorrowState,
198 {
199 let guard = Backend::read(self.storage.as_ref()).map_err(SharedStateError::Storage)?;
200 StateRef::from_guard(guard).map(State::from_inner)
201 }
202
203 /// Mutably borrows the runtime value and tracks the guard's final state.
204 ///
205 /// When the returned guard is dropped, the shared container is updated to
206 /// the guard's pending state. This allows methods on `State<SMutView<...>>`
207 /// to retain compile-time transition checks while the committed state is
208 /// stored at runtime.
209 ///
210 /// ```ignore
211 /// {
212 /// let connected = shared.borrow_mut::<Connected>()?;
213 /// let authenticated = transition!(connected, "alice".to_owned());
214 /// drop(authenticated); // commits `Authenticated`.
215 /// }
216 ///
217 /// let authenticated = shared.borrow::<Authenticated>()?;
218 /// ```
219 pub fn borrow_mut<RequestedState>(
220 &self,
221 ) -> Result<
222 SMutView<'_, Backend, T, RuntimeStateMarker<RequestedState>>,
223 SharedStateError<Backend::WriteError<'_, T>>,
224 >
225 where
226 RequestedState:
227 StateTrait + StateMarker + StateRuntimeMarkerFor<<RequestedState as StateMarker>::Kind>,
228 RuntimeStateMarker<RequestedState>: SharedBorrowState,
229 {
230 let guard = Backend::write(self.storage.as_ref()).map_err(SharedStateError::Storage)?;
231 StateMut::from_guard(guard).map(State::from_inner)
232 }
233}
234
235/// Shared state backed by `Rc<Storage::Storage<T>>`.
236///
237/// Use this alias when you want single-threaded aliasing but want to choose
238/// the synchronization cell yourself. The first type parameter is the
239/// [`SharedStorage`] backend, not the actual `Rc` payload:
240///
241/// ```ignore
242/// use magicstatemachines::{RefCellStorage, SRc};
243///
244/// let shared: SRc<RefCellStorage, Connection> =
245/// SRc::new::<Disconnected>(Connection::new("localhost:8080"));
246/// ```
247///
248/// Most code should use [`SRcRefCell`] unless it is intentionally exercising a
249/// custom backend.
250pub type SRc<Storage, T> = SharedState<Rc<<Storage as SharedStorage>::Storage<T>>, Storage, T>;
251/// Shared state backed by `Arc<Storage::Storage<T>>`.
252///
253/// This is the thread-safe counterpart to [`SRc`]. It is useful when the
254/// backend is selected by a public type alias or a generic parameter:
255///
256/// ```ignore
257/// use magicstatemachines::{MutexStorage, SArc};
258///
259/// type SharedConnection = SArc<MutexStorage, Connection>;
260///
261/// let shared = SharedConnection::new::<Disconnected>(
262/// Connection::new("localhost:8080"),
263/// );
264/// ```
265///
266/// Use [`SArcMutex`] or [`SArcRwLock`] for the built-in backends when no
267/// custom storage choice is needed.
268pub type SArc<Storage, T> = SharedState<Arc<<Storage as SharedStorage>::Storage<T>>, Storage, T>;
269/// Shared state backed by `Rc<RefCell<...>>`.
270///
271/// This is the default single-threaded shared-state container. It preserves
272/// the native `RefCell` error behavior: borrowing mutably while an immutable
273/// borrow is alive returns [`SharedStateError::Storage`] containing
274/// `std::cell::BorrowMutError`; asking for a state that is not committed
275/// returns [`SharedStateError::WrongState`].
276pub type SRcRefCell<T> = SRc<RefCellStorage, T>;
277/// Shared state backed by `Arc<Mutex<...>>`.
278///
279/// `borrow` and `borrow_mut` both acquire the mutex with `try_lock`, so a
280/// concurrent borrow reports the standard `TryLockError` through
281/// [`SharedStateError::Storage`] instead of blocking the caller.
282pub type SArcMutex<T> = SArc<MutexStorage, T>;
283/// Shared state backed by `Arc<RwLock<...>>`.
284///
285/// Immutable borrows use `try_read` and can coexist with other immutable
286/// borrows. Mutable borrows use `try_write` and fail with the backend's
287/// `TryLockError` while readers or another writer are alive.
288pub type SArcRwLock<T> = SArc<RwLockStorage, T>;
289/// Mutable-guard storage backend for [`RefCellStorage`].
290///
291/// This is the `Storage` parameter of a state returned by
292/// [`SRcRefCell::borrow_mut`]. It is useful in signatures when a method wants
293/// to specifically accept a `RefCell` guard rather than any [`crate::SMut`]
294/// storage:
295///
296/// ```ignore
297/// fn only_ref_cell_guard(
298/// state: magicstatemachines::State<
299/// magicstatemachines::SRefCell<'_>,
300/// Connection,
301/// Connected,
302/// >,
303/// ) {
304/// drop(state);
305/// }
306/// ```
307///
308/// State-machine implementation methods usually prefer `S: SMut` so they also
309/// work with owned, boxed, mutex, and custom storage.
310pub type SRefCell<'a> = StorageStateMut<'a, RefCellStorage>;
311/// Mutable-guard storage backend for [`MutexStorage`].
312///
313/// This is the concrete guard storage used by [`SArcMutex::borrow_mut`].
314/// Prefer a generic `S: SMut` bound unless you intentionally need to restrict
315/// a function to mutex-backed shared state.
316pub type SMutex<'a> = StorageStateMut<'a, MutexStorage>;
317/// Mutable-guard storage backend for [`RwLockStorage`].
318///
319/// This is the concrete guard storage used by [`SArcRwLock::borrow_mut`].
320/// It represents an active write guard whose final typestate will be committed
321/// back to the `RwLock` when the returned [`State`] is dropped.
322pub type SRwLock<'a> = StorageStateMut<'a, RwLockStorage>;
323/// State view held by a mutable guard from a shared storage backend.
324///
325/// The alias is mainly documentation for return types. For example,
326/// `SArcMutex<T>::borrow_mut::<Connected>()` returns an
327/// `SMutView<'_, MutexStorage, T, Connected>`. In user-facing methods, prefer
328/// the shorter arbitrary-self receiver form:
329///
330/// ```ignore
331/// fn authenticate<S>(
332/// self: magicstatemachines::State<S, Self, Connected>,
333/// user: String,
334/// ) -> magicstatemachines::State<S, Self, Authenticated>
335/// where
336/// S: magicstatemachines::SMut,
337/// {
338/// magicstatemachines::transition!(self, user)
339/// }
340/// ```
341pub type SMutView<'a, Backend, T, S> = State<StorageStateMut<'a, Backend>, T, S>;
342/// State view held by an immutable guard from a shared storage backend.
343///
344/// This is the return type of [`SharedState::borrow`]. It implements [`SRef`](crate::SRef)
345/// but not [`SMut`](crate::SMut), so it supports read-only arbitrary-self
346/// receivers such as `self: &State<impl SRef, Self, impl InOnline>` while
347/// preventing generated transitions from completing.
348pub type SRefView<'a, Backend, T, S> = State<StorageStateRef<'a, Backend>, T, S>;