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}