Skip to main content

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>;