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