Skip to main content

magicstatemachines/shared/
mod.rs

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