Repository: mkroening/one-shot-mutex
Pull request #5: Add `!Sync` variants
Author: login: nspin
Created: 2025-03-18T08:46:08Z | Merged: 2025-03-21T09:41:14Z

This PR adds:

- `RawUnsyncOneShotMutex`
- `RawUnsyncOneShotRwLock`

and their friends:

- `UnsyncOneShotMutex`
- `UnsyncOneShotMutexGuard`
- `UnsyncOneShotRwLock`
- `UnsyncOneShotRwLockReadGuard`
- `UnsyncOneShotRwLockUpgradableReadGuard`
- `UnsyncOneShotRwLockWriteGuard`

These can be implemented slightly more efficiently than their `Sync` counterparts, which makes them useful in cases where `Sync` is unnecessary.

Consider the case of an API that abstracts over a `RawMutex` parameter, and its usage in a downstream case where `Sync` is unnecessary. These `!Sync` `RawMutex`s allow for that API to be used without the overhead introduced by atomics, and with no more overhead than a `RefCell`.

As for the internal module structure introduced, I've placed these new items in a new `crate::unsync` super-module which mirrors the structure of the top-level to highlight the similarities between the new files and their sync analogs. Let me know if you'd like me to use a different module structure.

Also, I haven't done anything to deduplicate test code. I can introduce some abstraction into the crate's tests to reduce duplication if you think that would be worth the added complexity.


Diff:
diff --git a/src/lib.rs b/src/lib.rs
index 5d1e983..f868203 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,15 +4,9 @@
 //! This is useful in situations where contention would be a bug,
 //! such as in single-threaded programs that would deadlock on contention.
 //!
-//! See the [`RawOneShotMutex`] and [`RawOneShotRwLock`] types for more information.
+//! See the [`sync::RawOneShotMutex`] and [`sync::RawOneShotRwLock`] types for more information.
 
 #![no_std]
 
-mod mutex;
-mod rwlock;
-
-pub use mutex::{OneShotMutex, OneShotMutexGuard, RawOneShotMutex};
-pub use rwlock::{
-    OneShotRwLock, OneShotRwLockReadGuard, OneShotRwLockUpgradableReadGuard,
-    OneShotRwLockWriteGuard, RawOneShotRwLock,
-};
+pub mod sync;
+pub mod unsync;
diff --git a/src/sync/mod.rs b/src/sync/mod.rs
new file mode 100644
index 0000000..e98bdde
--- /dev/null
+++ b/src/sync/mod.rs
@@ -0,0 +1,10 @@
+//! One-shot lock variants that implement `Sync`.
+
+mod mutex;
+mod rwlock;
+
+pub use mutex::{OneShotMutex, OneShotMutexGuard, RawOneShotMutex};
+pub use rwlock::{
+    OneShotRwLock, OneShotRwLockReadGuard, OneShotRwLockUpgradableReadGuard,
+    OneShotRwLockWriteGuard, RawOneShotRwLock,
+};
diff --git a/src/mutex.rs b/src/sync/mutex.rs
similarity index 98%
rename from src/mutex.rs
rename to src/sync/mutex.rs
index 7a8cccd..9fed130 100644
--- a/src/mutex.rs
+++ b/src/sync/mutex.rs
@@ -15,7 +15,7 @@ use lock_api::{GuardSend, RawMutex, RawMutexFair};
 /// # Examples
 ///
 /// ```
-/// use one_shot_mutex::OneShotMutex;
+/// use one_shot_mutex::sync::OneShotMutex;
 ///
 /// static X: OneShotMutex<i32> = OneShotMutex::new(42);
 ///
diff --git a/src/rwlock.rs b/src/sync/rwlock.rs
similarity index 99%
rename from src/rwlock.rs
rename to src/sync/rwlock.rs
index b78f16c..a5825cd 100644
--- a/src/rwlock.rs
+++ b/src/sync/rwlock.rs
@@ -19,7 +19,7 @@ use lock_api::{
 /// # Examples
 ///
 /// ```
-/// use one_shot_mutex::OneShotRwLock;
+/// use one_shot_mutex::sync::OneShotRwLock;
 ///
 /// static X: OneShotRwLock<i32> = OneShotRwLock::new(42);
 ///
diff --git a/src/unsync/mod.rs b/src/unsync/mod.rs
new file mode 100644
index 0000000..9753fb1
--- /dev/null
+++ b/src/unsync/mod.rs
@@ -0,0 +1,15 @@
+//! One-shot lock variants that do not implement `Sync`.
+//!
+//! These one-shot locks not implement `Sync`, which permits slightly more efficient
+//! implementations.
+//!
+//! For variants that do implement `Sync`, see the [`sync`](crate::sync) module.
+
+mod mutex;
+mod rwlock;
+
+pub use mutex::{OneShotMutex, OneShotMutexGuard, RawOneShotMutex};
+pub use rwlock::{
+    OneShotRwLock, OneShotRwLockReadGuard, OneShotRwLockUpgradableReadGuard,
+    OneShotRwLockWriteGuard, RawOneShotRwLock,
+};
diff --git a/src/unsync/mutex.rs b/src/unsync/mutex.rs
new file mode 100644
index 0000000..a043574
--- /dev/null
+++ b/src/unsync/mutex.rs
@@ -0,0 +1,136 @@
+use core::cell::Cell;
+
+use lock_api::{GuardSend, RawMutex, RawMutexFair};
+
+/// A one-shot mutex that panics instead of (dead)locking on contention.
+///
+/// This mutex allows no contention and panics instead of blocking on [`lock`] if it is already locked.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This mutex does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`sync::RawOneShotMutex`](crate::sync::RawOneShotMutex).
+///
+/// This mutex should be used through [`OneShotMutex`].
+///
+/// [`lock`]: Self::lock
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::unsync::OneShotMutex;
+///
+/// let m: OneShotMutex<i32> = OneShotMutex::new(42);
+///
+/// // This is equivalent to `X.try_lock().unwrap()`.
+/// let x = m.lock();
+///
+/// // This panics instead of deadlocking.
+/// // let x2 = m.lock();
+///
+/// // Once we unlock the mutex, we can lock it again.
+/// drop(x);
+/// let x = m.lock();
+/// ```
+pub struct RawOneShotMutex {
+    lock: Cell<bool>,
+}
+
+impl RawOneShotMutex {
+    pub const fn new() -> Self {
+        Self::INIT
+    }
+}
+
+impl Default for RawOneShotMutex {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+unsafe impl RawMutex for RawOneShotMutex {
+    #[allow(clippy::declare_interior_mutable_const)]
+    const INIT: Self = Self {
+        lock: Cell::new(false),
+    };
+
+    type GuardMarker = GuardSend;
+
+    #[inline]
+    fn lock(&self) {
+        assert!(
+            self.try_lock(),
+            "called `lock` on a `RawOneShotMutex` that is already locked"
+        );
+    }
+
+    #[inline]
+    fn try_lock(&self) -> bool {
+        let was_locked = self.lock.replace(true);
+        !was_locked
+    }
+
+    #[inline]
+    unsafe fn unlock(&self) {
+        self.lock.set(false);
+    }
+
+    #[inline]
+    fn is_locked(&self) -> bool {
+        self.lock.get()
+    }
+}
+
+unsafe impl RawMutexFair for RawOneShotMutex {
+    #[inline]
+    unsafe fn unlock_fair(&self) {
+        unsafe { self.unlock() }
+    }
+
+    #[inline]
+    unsafe fn bump(&self) {}
+}
+
+/// A [`lock_api::Mutex`] based on [`RawOneShotMutex`].
+pub type OneShotMutex<T> = lock_api::Mutex<RawOneShotMutex, T>;
+
+/// A [`lock_api::MutexGuard`] based on [`RawOneShotMutex`].
+pub type OneShotMutexGuard<'a, T> = lock_api::MutexGuard<'a, RawOneShotMutex, T>;
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn lock() {
+        let mutex = OneShotMutex::new(42);
+        let mut guard = mutex.lock();
+        assert_eq!(*guard, 42);
+
+        *guard += 1;
+        drop(guard);
+        let guard = mutex.lock();
+        assert_eq!(*guard, 43);
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_panic() {
+        let mutex = OneShotMutex::new(42);
+        let _guard = mutex.lock();
+        let _guard2 = mutex.lock();
+    }
+
+    #[test]
+    fn try_lock() {
+        let mutex = OneShotMutex::new(42);
+        let mut guard = mutex.try_lock().unwrap();
+        assert_eq!(*guard, 42);
+        assert!(mutex.try_lock().is_none());
+
+        *guard += 1;
+        drop(guard);
+        let guard = mutex.try_lock().unwrap();
+        assert_eq!(*guard, 43);
+    }
+}
diff --git a/src/unsync/rwlock.rs b/src/unsync/rwlock.rs
new file mode 100644
index 0000000..a0e3078
--- /dev/null
+++ b/src/unsync/rwlock.rs
@@ -0,0 +1,395 @@
+use core::cell::Cell;
+
+use lock_api::{
+    GuardSend, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade,
+    RawRwLockUpgradeDowngrade,
+};
+
+/// A one-shot readers-writer lock that panics instead of (dead)locking on contention.
+///
+/// This lock allows no contention and panics on [`lock_shared`], [`lock_exclusive`], [`lock_upgradable`], and [`upgrade`] if it is already locked conflictingly.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This lock does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`sync::RawOneShotRwLock`](crate::sync::RawOneShotRwLock).
+///
+/// [`lock_shared`]: RawOneShotRwLock::lock_shared
+/// [`lock_exclusive`]: RawOneShotRwLock::lock_exclusive
+/// [`lock_upgradable`]: RawOneShotRwLock::lock_upgradable
+/// [`upgrade`]: RawOneShotRwLock::upgrade
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::unsync::OneShotRwLock;
+///
+/// let m: OneShotRwLock<i32> = OneShotRwLock::new(42);
+///
+/// // This is equivalent to `X.try_write().unwrap()`.
+/// let x = m.write();
+///
+/// // This panics instead of deadlocking.
+/// // let x2 = m.write();
+///
+/// // Once we unlock the mutex, we can lock it again.
+/// drop(x);
+/// let x = m.write();
+/// ```
+pub struct RawOneShotRwLock {
+    lock: Cell<usize>,
+}
+
+/// Normal shared lock counter
+const SHARED: usize = 1 << 2;
+/// Special upgradable shared lock flag
+const UPGRADABLE: usize = 1 << 1;
+/// Exclusive lock flag
+const EXCLUSIVE: usize = 1;
+
+impl RawOneShotRwLock {
+    pub const fn new() -> Self {
+        Self::INIT
+    }
+
+    #[inline]
+    fn over_state(&self, f: impl FnOnce(usize) -> usize) -> usize {
+        let old = self.lock.get();
+        self.lock.set(f(old));
+        old
+    }
+
+    #[inline]
+    fn is_locked_shared(&self) -> bool {
+        self.lock.get() & !(EXCLUSIVE | UPGRADABLE) != 0
+    }
+
+    #[inline]
+    fn is_locked_upgradable(&self) -> bool {
+        self.lock.get() & UPGRADABLE == UPGRADABLE
+    }
+
+    /// Acquire a shared lock, returning the new lock value.
+    #[inline]
+    fn acquire_shared(&self) -> usize {
+        let value = self.over_state(|state| state + SHARED);
+
+        // An arbitrary cap that allows us to catch overflows long before they happen
+        if value > usize::MAX / 2 {
+            self.over_state(|state| state - SHARED);
+            panic!("Too many shared locks, cannot safely proceed");
+        }
+
+        value
+    }
+}
+
+impl Default for RawOneShotRwLock {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+unsafe impl RawRwLock for RawOneShotRwLock {
+    #[allow(clippy::declare_interior_mutable_const)]
+    const INIT: Self = Self { lock: Cell::new(0) };
+
+    type GuardMarker = GuardSend;
+
+    #[inline]
+    fn lock_shared(&self) {
+        assert!(
+            self.try_lock_shared(),
+            "called `lock_shared` on a `RawOneShotRwLock` that is already locked exclusively"
+        );
+    }
+
+    #[inline]
+    fn try_lock_shared(&self) -> bool {
+        let value = self.acquire_shared();
+
+        let acquired = value & EXCLUSIVE != EXCLUSIVE;
+
+        if !acquired {
+            unsafe {
+                self.unlock_shared();
+            }
+        }
+
+        acquired
+    }
+
+    #[inline]
+    unsafe fn unlock_shared(&self) {
+        debug_assert!(self.is_locked_shared());
+
+        self.over_state(|state| state - SHARED);
+    }
+
+    #[inline]
+    fn lock_exclusive(&self) {
+        assert!(
+            self.try_lock_exclusive(),
+            "called `lock_exclusive` on a `RawOneShotRwLock` that is already locked"
+        );
+    }
+
+    #[inline]
+    fn try_lock_exclusive(&self) -> bool {
+        let ok = self.lock.get() == 0;
+        if ok {
+            self.lock.set(EXCLUSIVE);
+        }
+        ok
+    }
+
+    #[inline]
+    unsafe fn unlock_exclusive(&self) {
+        debug_assert!(self.is_locked_exclusive());
+
+        self.over_state(|state| state & !EXCLUSIVE);
+    }
+
+    #[inline]
+    fn is_locked(&self) -> bool {
+        self.lock.get() != 0
+    }
+
+    #[inline]
+    fn is_locked_exclusive(&self) -> bool {
+        self.lock.get() & EXCLUSIVE == EXCLUSIVE
+    }
+}
+
+unsafe impl RawRwLockRecursive for RawOneShotRwLock {
+    #[inline]
+    fn lock_shared_recursive(&self) {
+        self.lock_shared();
+    }
+
+    #[inline]
+    fn try_lock_shared_recursive(&self) -> bool {
+        self.try_lock_shared()
+    }
+}
+
+unsafe impl RawRwLockDowngrade for RawOneShotRwLock {
+    #[inline]
+    unsafe fn downgrade(&self) {
+        // Reserve the shared guard for ourselves
+        self.acquire_shared();
+
+        unsafe {
+            self.unlock_exclusive();
+        }
+    }
+}
+
+unsafe impl RawRwLockUpgrade for RawOneShotRwLock {
+    #[inline]
+    fn lock_upgradable(&self) {
+        assert!(
+            self.try_lock_upgradable(),
+            "called `lock_upgradable` on a `RawOneShotRwLock` that is already locked upgradably or exclusively"
+        );
+    }
+
+    #[inline]
+    fn try_lock_upgradable(&self) -> bool {
+        let value = self.over_state(|state| state | UPGRADABLE);
+
+        let acquired = value & (UPGRADABLE | EXCLUSIVE) == 0;
+
+        if !acquired && value & UPGRADABLE == 0 {
+            unsafe {
+                self.unlock_upgradable();
+            }
+        }
+
+        acquired
+    }
+
+    #[inline]
+    unsafe fn unlock_upgradable(&self) {
+        debug_assert!(self.is_locked_upgradable());
+
+        self.over_state(|state| state & !UPGRADABLE);
+    }
+
+    #[inline]
+    unsafe fn upgrade(&self) {
+        assert!(
+            self.try_upgrade(),
+            "called `upgrade` on a `RawOneShotRwLock` that is also locked shared by others"
+        );
+    }
+
+    #[inline]
+    unsafe fn try_upgrade(&self) -> bool {
+        let ok = self.lock.get() == UPGRADABLE;
+        if ok {
+            self.lock.set(EXCLUSIVE);
+        }
+        ok
+    }
+}
+
+unsafe impl RawRwLockUpgradeDowngrade for RawOneShotRwLock {
+    #[inline]
+    unsafe fn downgrade_upgradable(&self) {
+        self.acquire_shared();
+
+        unsafe {
+            self.unlock_upgradable();
+        }
+    }
+
+    #[inline]
+    unsafe fn downgrade_to_upgradable(&self) {
+        debug_assert!(self.is_locked_exclusive());
+
+        self.over_state(|state| state ^ (UPGRADABLE | EXCLUSIVE));
+    }
+}
+
+/// A [`lock_api::RwLock`] based on [`RawOneShotRwLock`].
+pub type OneShotRwLock<T> = lock_api::RwLock<RawOneShotRwLock, T>;
+
+/// A [`lock_api::RwLockReadGuard`] based on [`RawOneShotRwLock`].
+pub type OneShotRwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawOneShotRwLock, T>;
+
+/// A [`lock_api::RwLockUpgradableReadGuard`] based on [`RawOneShotRwLock`].
+pub type OneShotRwLockUpgradableReadGuard<'a, T> =
+    lock_api::RwLockUpgradableReadGuard<'a, RawOneShotRwLock, T>;
+
+/// A [`lock_api::RwLockWriteGuard`] based on [`RawOneShotRwLock`].
+pub type OneShotRwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawOneShotRwLock, T>;
+
+#[cfg(test)]
+mod tests {
+    use lock_api::RwLockUpgradableReadGuard;
+
+    use super::*;
+
+    #[test]
+    fn lock_exclusive() {
+        let lock = OneShotRwLock::new(42);
+        let mut guard = lock.write();
+        assert_eq!(*guard, 42);
+
+        *guard += 1;
+        drop(guard);
+        let guard = lock.write();
+        assert_eq!(*guard, 43);
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_exclusive_panic() {
+        let lock = OneShotRwLock::new(42);
+        let _guard = lock.write();
+        let _guard2 = lock.write();
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_exclusive_shared_panic() {
+        let lock = OneShotRwLock::new(42);
+        let _guard = lock.write();
+        let _guard2 = lock.read();
+    }
+
+    #[test]
+    fn try_lock_exclusive() {
+        let lock = OneShotRwLock::new(42);
+        let mut guard = lock.try_write().unwrap();
+        assert_eq!(*guard, 42);
+        assert!(lock.try_write().is_none());
+
+        *guard += 1;
+        drop(guard);
+        let guard = lock.try_write().unwrap();
+        assert_eq!(*guard, 43);
+    }
+
+    #[test]
+    fn lock_shared() {
+        let lock = OneShotRwLock::new(42);
+        let guard = lock.read();
+        assert_eq!(*guard, 42);
+        let guard2 = lock.read();
+        assert_eq!(*guard2, 42);
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_shared_panic() {
+        let lock = OneShotRwLock::new(42);
+        let _guard = lock.write();
+        let _guard2 = lock.read();
+    }
+
+    #[test]
+    fn try_lock_shared() {
+        let lock = OneShotRwLock::new(42);
+        let guard = lock.try_read().unwrap();
+        assert_eq!(*guard, 42);
+        assert!(lock.try_write().is_none());
+
+        let guard2 = lock.try_read().unwrap();
+        assert_eq!(*guard2, 42);
+    }
+
+    #[test]
+    fn lock_upgradable() {
+        let lock = OneShotRwLock::new(42);
+        let guard = lock.upgradable_read();
+        assert_eq!(*guard, 42);
+        assert!(lock.try_write().is_none());
+
+        let mut upgraded = RwLockUpgradableReadGuard::upgrade(guard);
+        *upgraded += 1;
+        drop(upgraded);
+        let guard2 = lock.upgradable_read();
+        assert_eq!(*guard2, 43);
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_upgradable_panic() {
+        let lock = OneShotRwLock::new(42);
+        let _guard = lock.upgradable_read();
+        let _guard2 = lock.upgradable_read();
+    }
+
+    #[test]
+    #[should_panic]
+    fn lock_upgradable_write_panic() {
+        let lock = OneShotRwLock::new(42);
+        let _guard = lock.write();
+        let _guard2 = lock.upgradable_read();
+    }
+
+    #[test]
+    fn try_lock_upgradable() {
+        let lock = OneShotRwLock::new(42);
+        let guard = lock.try_upgradable_read().unwrap();
+        assert_eq!(*guard, 42);
+        assert!(lock.try_write().is_none());
+
+        let mut upgraded = RwLockUpgradableReadGuard::try_upgrade(guard).unwrap();
+        *upgraded += 1;
+        drop(upgraded);
+        let guard2 = lock.try_upgradable_read().unwrap();
+        assert_eq!(*guard2, 43);
+    }
+
+    #[test]
+    #[should_panic]
+    fn upgrade_panic() {
+        let lock = OneShotRwLock::new(42);
+        let guard = lock.upgradable_read();
+        let _guard2 = lock.read();
+        let _guard3 = RwLockUpgradableReadGuard::upgrade(guard);
+    }
+}


Reviews:
user: login: mkroening
body: The code looks good, I just have three minor comments.

I think I would favor `crate::sync::`/`crate::unsync::`. You could either do this in this PR, or I can do it afterward, before releasing 0.2.0.
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-20T10:55:32Z
user: login: nspin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:52:23Z
user: login: nspin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:52:26Z
user: login: nspin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:52:30Z
user: login: mkroening
body: Thank you very much! :)
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-21T09:41:09Z

Review comments:
diff_hunk: @@ -0,0 +1,13 @@
+//! One-shot lock variants that do not implement `Sync`.
+//!
+//! These one-shot locks not implement `Sync`, which permits slightly more efficient
+//! implementations.
+
+pub(crate) mod mutex;
+pub(crate) mod rwlock;
path: src/unsync/mod.rs
user: login: mkroening
body: We can make these private now, right?
created_at: 2025-03-20T10:48:43Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,136 @@
+use core::cell::Cell;
+
+use lock_api::{GuardSend, RawMutex, RawMutexFair};
+
+/// A one-shot mutex that panics instead of (dead)locking on contention.
+///
+/// This mutex allows no contention and panics instead of blocking on [`lock`] if it is already locked.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This mutex does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`RawOneShotMutex`](crate::RawOneShotMutex).
+///
+/// This mutex should be used through [`OneShotMutex`].
+///
+/// [`lock`]: Self::lock
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::OneShotMutex;
path: src/unsync/mutex.rs
user: login: mkroening
body: This should use the unsync variant.
created_at: 2025-03-20T10:51:53Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,395 @@
+use core::cell::Cell;
+
+use lock_api::{
+    GuardSend, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade,
+    RawRwLockUpgradeDowngrade,
+};
+
+/// A one-shot readers-writer lock that panics instead of (dead)locking on contention.
+///
+/// This lock allows no contention and panics on [`lock_shared`], [`lock_exclusive`], [`lock_upgradable`], and [`upgrade`] if it is already locked conflictingly.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This lock does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`RawOneShotRwLock`](crate::RawOneShotRwLock).
+///
+/// [`lock_shared`]: RawOneShotRwLock::lock_shared
+/// [`lock_exclusive`]: RawOneShotRwLock::lock_exclusive
+/// [`lock_upgradable`]: RawOneShotRwLock::lock_upgradable
+/// [`upgrade`]: RawOneShotRwLock::upgrade
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::OneShotRwLock;
path: src/unsync/rwlock.rs
user: login: mkroening
body: This should also use the unsync variant.
created_at: 2025-03-20T10:52:13Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,13 @@
+//! One-shot lock variants that do not implement `Sync`.
+//!
+//! These one-shot locks not implement `Sync`, which permits slightly more efficient
+//! implementations.
+
+pub(crate) mod mutex;
+pub(crate) mod rwlock;
path: src/unsync/mod.rs
user: login: nspin
body: Oops, missed this. Done.
created_at: 2025-03-21T00:52:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,136 @@
+use core::cell::Cell;
+
+use lock_api::{GuardSend, RawMutex, RawMutexFair};
+
+/// A one-shot mutex that panics instead of (dead)locking on contention.
+///
+/// This mutex allows no contention and panics instead of blocking on [`lock`] if it is already locked.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This mutex does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`RawOneShotMutex`](crate::RawOneShotMutex).
+///
+/// This mutex should be used through [`OneShotMutex`].
+///
+/// [`lock`]: Self::lock
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::OneShotMutex;
path: src/unsync/mutex.rs
user: login: nspin
body: Done.
created_at: 2025-03-21T00:52:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,395 @@
+use core::cell::Cell;
+
+use lock_api::{
+    GuardSend, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade,
+    RawRwLockUpgradeDowngrade,
+};
+
+/// A one-shot readers-writer lock that panics instead of (dead)locking on contention.
+///
+/// This lock allows no contention and panics on [`lock_shared`], [`lock_exclusive`], [`lock_upgradable`], and [`upgrade`] if it is already locked conflictingly.
+/// This is useful in situations where contention would be a bug,
+/// such as in single-threaded programs that would deadlock on contention.
+///
+/// This lock does not implement `Sync`, which permits a slightly more efficient implementation.
+/// For a variant that does implement `Sync`, see [`RawOneShotRwLock`](crate::RawOneShotRwLock).
+///
+/// [`lock_shared`]: RawOneShotRwLock::lock_shared
+/// [`lock_exclusive`]: RawOneShotRwLock::lock_exclusive
+/// [`lock_upgradable`]: RawOneShotRwLock::lock_upgradable
+/// [`upgrade`]: RawOneShotRwLock::upgrade
+///
+/// # Examples
+///
+/// ```
+/// use one_shot_mutex::OneShotRwLock;
path: src/unsync/rwlock.rs
user: login: nspin
body: Done.
created_at: 2025-03-21T00:52:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: mkroening
created_at: 2025-03-20T09:24:39Z
body: Thanks for the PR!

Instead of exposing them not at the crate root with different names, we could expose them in the module with the same names.
So instead of having `RawUnsyncOneShotMutex`, we'd have `unsync::RawOneShotMutex`.
Following this train of thought, it might even make sense to move the current exports from the crate root into a `sync` module, similar to how [once_cell](https://docs.rs/once_cell) does it.

What do you think about this?

> Also, I haven't done anything to deduplicate test code.

That's fine, I think.
author_association: OWNER
user: login: nspin
created_at: 2025-03-20T10:06:36Z
body: > Instead of exposing them not at the crate root with different names, we could expose them in the module with the same names.

This is a good idea, as their behavior is identical to that of their `Sync` counterparts, aside from the difference in trait bounds. I've updated the PR to follow this layout.

> Following this train of thought, it might even make sense to move the current exports from the crate root into a sync module, similar to how [once_cell](https://docs.rs/once_cell) does it.

I think either approach, `crate::`/`crate::unsync::` or `crate::sync::`/`crate::unsync::`, could work here.
author_association: CONTRIBUTOR
user: login: nspin
created_at: 2025-03-21T00:53:17Z
body: I've added a second commit which moves the `Sync` variants into `crate::sync`.
author_association: CONTRIBUTOR
user: login: mkroening
created_at: 2025-03-21T09:48:24Z
body: This is now released as 0.2.0 (and 0.2.1). 👍 
author_association: OWNER