mutex_traits/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
5
6/// Const Init Trait
7///
8/// This trait is intended for use when implementers of [`ScopedRawMutex`] that can
9/// be constructed in const context, e.g. for placing in a `static`
10pub trait ConstInit {
11    /// Create a new instance.
12    ///
13    /// This is a const instead of a method to allow creating instances in const context.
14    const INIT: Self;
15}
16
17/// Raw scoped mutex trait.
18///
19/// This mutex is "raw", which means it does not actually contain the protected data, it
20/// just implements the mutex mechanism. For most uses you should use `BlockingMutex`
21/// from the `scoped-mutex-impls` crate instead, which is generic over a
22/// `ScopedRawMutex` and contains the protected data.
23///
24/// # Safety
25///
26/// ScopedRawMutex implementations must ensure that, while locked, no other thread can lock
27/// the RawMutex concurrently. This can usually be implemented using an [`AtomicBool`]
28/// to track the "taken" state. See the `scoped-mutex-impls` crate for examples of
29/// correct implementations.
30///
31/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior.
32///
33/// [`AtomicBool`]: core::sync::atomic::AtomicBool
34pub unsafe trait ScopedRawMutex {
35    /// Lock this `ScopedRawMutex`, calling `f()` after the lock has been acquired, and releasing
36    /// the lock after the completion of `f()`.
37    ///
38    /// If this was successful, `Some(R)` will be returned. If the mutex was already locked,
39    /// `None` will be returned
40    #[must_use]
41    fn try_with_lock<R>(&self, f: impl FnOnce() -> R) -> Option<R>;
42
43    /// Lock this `ScopedRawMutex`, calling `f()` after the lock has been acquired, and releasing
44    /// the lock after the completion of `f()`.
45    ///
46    /// Implementors may choose whether to block or panic if the lock is already locked.
47    /// It is recommended to panic if it is possible to know that deadlock has occurred.
48    ///
49    /// For implementations on a system with threads, blocking may be the correct choice.
50    ///
51    /// For implementations where a single thread is present, panicking immediately may be
52    /// the correct choice.
53    fn with_lock<R>(&self, f: impl FnOnce() -> R) -> R;
54
55    /// Is this mutex currently locked?
56    fn is_locked(&self) -> bool;
57}
58
59/// Raw mutex trait.
60///
61/// This trait represents an implementation of a generic mutual-exclusion lock
62/// which may be locked and unlocked freely at any time.
63///
64/// This mutex is "raw", which means it does not actually contain the protected
65/// data, it just implements the mutex mechanism. For most uses you should use
66/// `BlockingMutex`  from the `mutex` crate instead, which is generic over a
67/// `RawMutex` and contains the protected data.
68///
69/// # `RawMutex` and [`ScopedRawMutex`]
70///
71/// The `RawMutex` trait is a superset of the [`ScopedRawMutex`] trait. The
72/// interface defined in [`ScopedRawMutex`] is more restrictive, and only
73/// permits the mutex to be locked for the duration of a single [`FnOnce`] call
74/// and unlocked immediately when that closure exits. `RawMutex`, on the other
75/// hand, permits a much wider range of potential usage patterns: it may be used
76/// to implement a RAII-style lock guard like [`std::sync::Mutex`][s], a
77/// "C-style" mutex where explicit `lock` and `unlock` calls have to be paired
78/// manually, *or* a scoped closure-based API like [`ScopedRawMutex`].
79/// Therefore, **there is [a blanket implementation][blanket] of
80/// [`ScopedRawMutex`] for all types that implement `RawMutex`**.
81///
82/// Some mutex implementations may not be able to implement the full `RawMutex`
83/// trait, and may only be able to implement the closure-based
84/// [`ScopedRawMutex`] subset. For example, implementations for the
85/// [`critical-section`] crate (in [`mutex::raw_impls::cs`][cs]) can only
86/// implement the `ScopedRawMutex` trait. However, in general, **mutex
87/// implementations that *can* implement the more general `RawMutex` trait
88/// should prefer to do so**, as they will be able to be used in code that
89/// requires either interface.
90///
91/// # Safety
92///
93/// Implementations of this trait must ensure that the mutex is actually
94/// exclusive: a lock can't be acquired while the mutex is already locked.
95///
96/// [blanket]: ScopedRawMutex#impl-ScopedRawMutex-for-M
97/// [s]: https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html
98/// [cs]: https://docs.rs/mutex/latest/mutex/raw_impls/cs/index.html
99/// [critical-section]: https://docs.rs/critical-section/latest/critical_section/
100pub unsafe trait RawMutex {
101    /// Marker type which determines whether a lock guard should be [`Send`].
102    type GuardMarker;
103
104    /// Acquires this mutex, blocking the current thread/CPU core until it is
105    /// able to do so.
106    fn lock(&self);
107
108    /// Attempts to acquire this mutex without blocking. Returns `true`
109    /// if the lock was successfully acquired and `false` otherwise.
110    fn try_lock(&self) -> bool;
111
112    /// Unlocks this mutex.
113    ///
114    /// # Safety
115    ///
116    /// This method may only be called if the mutex is held in the current
117    /// context, i.e. it must be paired with a successful call to [`lock`] or
118    /// [`try_lock`].
119    ///
120    /// [`lock`]: RawMutex::lock
121    /// [`try_lock`]: RawMutex::try_lock
122    unsafe fn unlock(&self);
123
124    /// Returns `true` if the mutex is currently locked.
125    fn is_locked(&self) -> bool;
126}
127
128unsafe impl<M: RawMutex> ScopedRawMutex for M {
129    #[inline]
130    #[track_caller]
131    fn try_with_lock<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
132        if self.try_lock() {
133            // Using a drop guard ensures that the mutex is unlocked when this
134            // function exits, even if `f()` panics.
135            let _unlock = Unlock(self);
136            Some(f())
137        } else {
138            None
139        }
140    }
141
142    #[inline]
143    #[track_caller]
144    fn with_lock<R>(&self, f: impl FnOnce() -> R) -> R {
145        self.lock();
146        // Using a drop guard ensures that the mutex is unlocked when this
147        // function exits, even if `f()` panics.
148        let _unlock = Unlock(self);
149        f()
150    }
151
152    /// Is this mutex currently locked?
153    #[inline]
154    fn is_locked(&self) -> bool {
155        RawMutex::is_locked(self)
156    }
157}
158
159/// Implementation detail of the `ScopedRawMutex` implementation for `RawMutex`.
160/// This is a drop guard that unlocks the `RawMutex` when it's dropped. This is
161/// used to ensure that the `RawMutex` is always unlocked when the
162/// `ScopedRawMutex::with_lock` or `ScopedRawMutex::try_with_lock` closures are
163/// exited, even if they are exited by a panic rather than by a normal return.
164struct Unlock<'mutex, M: RawMutex>(&'mutex M);
165
166impl<M: RawMutex> Drop for Unlock<'_, M> {
167    fn drop(&mut self) {
168        unsafe {
169            // Safety: Constructing an `Unlock` is only safe if the mutex has
170            // been locked. Callers are responsible for ensuring this invariant;
171            // since this struct is only constructed in this module, we do so.
172            self.0.unlock()
173        }
174    }
175}