#![cfg_attr(not(feature = "enable_std"), no_std)]
#![warn(
clippy::cargo,
clippy::complexity,
clippy::pedantic,
clippy::perf,
clippy::style,
clippy::suspicious,
clippy::undocumented_unsafe_blocks
)]
#[cfg(feature = "debug_lockcell")]
use core::panic::Location;
use core::{
borrow::{Borrow, BorrowMut},
cell::{Cell, UnsafeCell},
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
convert::{AsMut, AsRef, TryFrom},
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
mem::{self, ManuallyDrop},
ops::{Deref, DerefMut, FnOnce},
};
#[cfg(feature = "enable_std")]
use std::error::Error as StdError;
pub struct LockCell<T: ?Sized> {
is_locked: Cell<bool>,
#[cfg(feature = "debug_lockcell")]
first_locked_at: Cell<Option<&'static Location<'static>>>,
value: UnsafeCell<T>,
}
impl<T> LockCell<T> {
#[must_use]
#[inline]
pub const fn new(value: T) -> Self {
Self {
is_locked: Cell::new(false),
#[cfg(feature = "debug_lockcell")]
first_locked_at: Cell::new(None),
value: UnsafeCell::new(value),
}
}
#[must_use]
#[inline]
pub fn into_inner(self) -> T {
self.value.into_inner()
}
#[track_caller]
#[inline]
pub fn swap(&self, rhs: &LockCell<T>) {
mem::swap(&mut *self.lock(), &mut *rhs.lock());
}
#[inline]
#[track_caller]
pub fn replace(&self, new_value: T) -> T {
let mut lock = self.lock();
mem::replace(&mut *lock, new_value)
}
#[inline]
#[track_caller]
pub fn replace_with<F>(&self, f: F) -> T
where
F: FnOnce(&mut T) -> T,
{
let mut lock = self.lock();
let replacement = f(&mut *lock);
mem::replace(&mut *lock, replacement)
}
#[inline]
#[track_caller]
pub fn take(&self) -> T
where
T: Default,
{
self.replace(Default::default())
}
}
impl<T: ?Sized> LockCell<T> {
#[inline]
#[track_caller]
pub fn try_lock(&self) -> Result<LockGuard<'_, T>, TryLockError> {
if self.is_locked.replace(true) {
return Err(TryLockError::new(self));
}
#[cfg(feature = "debug_lockcell")]
{
self.first_locked_at.set(Some(Location::caller()));
}
Ok(LockGuard {
value: self.value.get(),
is_locked: &self.is_locked,
#[cfg(feature = "debug_lockcell")]
locked_at: &self.first_locked_at,
_boo: PhantomData,
})
}
#[inline]
#[track_caller]
pub fn lock(&self) -> LockGuard<'_, T> {
self.try_lock().expect("already locked")
}
#[must_use]
#[inline]
pub fn is_locked(&self) -> bool {
self.is_locked.get()
}
#[must_use]
#[inline]
pub fn get_mut(&mut self) -> &mut T {
self.value.get_mut()
}
#[must_use]
#[inline]
pub fn as_ptr(&self) -> *mut T {
self.value.get()
}
#[inline]
pub fn reset_lock(&mut self) -> &mut T {
self.is_locked.set(false);
#[cfg(feature = "debug_lockcell")]
{
self.first_locked_at.set(None);
}
self.get_mut()
}
}
impl<T: fmt::Debug> fmt::Debug for LockCell<T> {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
let lock_result = self.try_lock();
let value: &dyn fmt::Debug = if let Ok(value) = lock_result.as_deref() {
value
} else {
struct LockedPlaceholder;
impl fmt::Debug for LockedPlaceholder {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
fmtr.write_str("<locked>")
}
}
const PLACEHOLDER: LockedPlaceholder = LockedPlaceholder;
&PLACEHOLDER
};
fmtr.debug_struct("LockCell").field("value", value).finish()
}
}
impl<T: Default> Default for LockCell<T> {
#[inline]
fn default() -> Self {
Self::new(Default::default())
}
}
impl<T> From<T> for LockCell<T> {
#[inline]
fn from(value: T) -> Self {
Self::new(value)
}
}
#[must_use]
pub struct LockGuard<'lock, T: ?Sized> {
value: *mut T,
is_locked: &'lock Cell<bool>,
#[cfg(feature = "debug_lockcell")]
locked_at: &'lock Cell<Option<&'static Location<'static>>>,
_boo: PhantomData<&'lock UnsafeCell<T>>,
}
impl<'lock, T: ?Sized> LockGuard<'lock, T> {
#[inline]
pub fn map<F, U: ?Sized>(this: Self, func: F) -> LockGuard<'lock, U>
where
F: FnOnce(&mut T) -> &mut U,
{
let mut this = ManuallyDrop::new(this);
LockGuard {
value: unsafe { func(&mut *this.value) } as *mut _,
#[cfg(feature = "debug_lockcell")]
locked_at: this.locked_at,
is_locked: this.is_locked,
_boo: PhantomData,
}
}
#[inline]
pub fn filter_map<F, U: ?Sized>(this: Self, func: F) -> Result<LockGuard<'lock, U>, Self>
where
F: FnOnce(&mut T) -> Option<&mut U>,
{
let mut this = ManuallyDrop::new(this);
let value = match unsafe { func(&mut *this.value) } {
Some(value) => value as *mut _,
_ => return Err(ManuallyDrop::into_inner(this)),
};
Ok(LockGuard {
value: unsafe { &mut *value },
#[cfg(feature = "debug_lockcell")]
locked_at: this.locked_at,
is_locked: this.is_locked,
_boo: PhantomData,
})
}
}
impl<'lock, T> TryFrom<&'lock LockCell<T>> for LockGuard<'lock, T> {
type Error = TryLockError;
#[inline]
#[track_caller]
fn try_from(lock_cell: &'lock LockCell<T>) -> Result<Self, Self::Error> {
lock_cell.try_lock()
}
}
impl<'lock, T: ?Sized> Drop for LockGuard<'lock, T> {
#[inline]
fn drop(&mut self) {
self.is_locked.set(false);
#[cfg(feature = "debug_lockcell")]
{
self.locked_at.set(None);
}
}
}
impl<'lock, T: ?Sized> AsRef<T> for LockGuard<'lock, T> {
#[inline]
fn as_ref(&self) -> &T {
unsafe { &*self.value }
}
}
impl<'lock, T: ?Sized> AsMut<T> for LockGuard<'lock, T> {
#[inline]
fn as_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
}
}
impl<'lock, T: ?Sized> Borrow<T> for LockGuard<'lock, T> {
#[inline]
fn borrow(&self) -> &T {
unsafe { &*self.value }
}
}
impl<'lock, T: ?Sized> BorrowMut<T> for LockGuard<'lock, T> {
#[inline]
fn borrow_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
}
}
impl<'lock, T: ?Sized> Deref for LockGuard<'lock, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.value }
}
}
impl<'lock, T: ?Sized> DerefMut for LockGuard<'lock, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
}
}
impl<'lock, T: fmt::Debug + ?Sized> fmt::Debug for LockGuard<'lock, T> {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
fmtr.debug_struct("LockGuard").field("value", self).finish()
}
}
impl<'lock, T: fmt::Display + ?Sized> fmt::Display for LockGuard<'lock, T> {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
<T as fmt::Display>::fmt(self, fmtr)
}
}
impl<'lock, T: ?Sized + Hash> Hash for LockGuard<'lock, T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
<T as Hash>::hash(self, state);
}
}
impl<'lock, T: ?Sized + PartialEq> PartialEq<T> for LockGuard<'lock, T> {
#[inline]
fn eq(&self, other: &T) -> bool {
<T as PartialEq>::eq(self, other)
}
}
impl<'other, 'lock, T: ?Sized + PartialEq> PartialEq<LockGuard<'other, T>> for LockGuard<'lock, T> {
#[inline]
fn eq(&self, other: &LockGuard<'other, T>) -> bool {
<T as PartialEq>::eq(self, other)
}
}
impl<'lock, T: ?Sized + Eq> Eq for LockGuard<'lock, T> {}
impl<'lock, T: ?Sized + PartialOrd> PartialOrd<T> for LockGuard<'lock, T> {
#[inline]
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
<T as PartialOrd>::partial_cmp(self, other)
}
}
impl<'other, 'lock, T: ?Sized + PartialOrd> PartialOrd<LockGuard<'other, T>> for LockGuard<'lock, T> {
#[inline]
fn partial_cmp(&self, other: &LockGuard<'other, T>) -> Option<Ordering> {
<T as PartialOrd>::partial_cmp(self, other)
}
}
impl<'lock, T: ?Sized + Ord> Ord for LockGuard<'lock, T> {
#[inline]
fn cmp(&self, other: &LockGuard<'lock, T>) -> Ordering {
<T as Ord>::cmp(self, other)
}
}
#[non_exhaustive]
pub struct TryLockError {
#[cfg(feature = "debug_lockcell")]
first_lock_location: &'static Location<'static>,
#[cfg(feature = "debug_lockcell")]
latest_lock_location: &'static Location<'static>,
_priv: (),
}
impl TryLockError {
#[cfg_attr(not(feature = "debug_lockcell"), allow(unused_variables))]
#[track_caller]
#[inline]
fn new<T: ?Sized>(cell: &LockCell<T>) -> Self {
TryLockError {
#[cfg(feature = "debug_lockcell")]
first_lock_location: cell
.first_locked_at
.get()
.expect("Cell must be already locked"),
#[cfg(feature = "debug_lockcell")]
latest_lock_location: Location::caller(),
_priv: (),
}
}
}
impl fmt::Debug for TryLockError {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = fmtr.debug_struct("TryLockError");
#[cfg(feature = "debug_lockcell")]
{
builder.field("first_locked_at", &self.first_lock_location);
builder.field("last_locked_at", &self.latest_lock_location);
}
builder.finish_non_exhaustive()
}
}
impl fmt::Display for TryLockError {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "debug_lockcell")]
{
write!(
fmtr,
"first lock at {} conflicts with lock at {}",
self.first_lock_location, self.latest_lock_location,
)
}
#[cfg(not(feature = "debug_lockcell"))]
{
fmtr.write_str("already locked")
}
}
}
#[cfg(feature = "enable_std")]
impl StdError for TryLockError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locking() {
let mut mtx = LockCell::new(23);
let mut lk = mtx.lock();
assert_eq!(*lk, 23);
assert!(mtx.is_locked());
*lk = 32;
assert_eq!(*lk, 32);
assert!(mtx.try_lock().is_err());
drop(lk);
assert_eq!(*mtx.get_mut(), 32);
}
#[test]
fn lock_map() {
#[derive(Default, Debug)]
struct TestData {
x: i32,
y: i32,
}
let mtx = LockCell::<TestData>::default();
let mut lk = LockGuard::map(mtx.lock(), |test_data| &mut test_data.y);
*lk = 42;
drop(lk);
let lk = mtx.lock();
let mut lk = match LockGuard::filter_map(lk, |data| Some(&mut data.x)) {
Ok(new_lk) => new_lk,
Err(old_lk) => panic!("{:?}", old_lk),
};
assert!(mtx.is_locked());
*lk = 21;
assert_eq!(*lk, 21);
match LockGuard::filter_map(lk, |_| -> Option<&mut i32> { None }) {
Ok(new_lk) => panic!("Unexpected lock guard found: {:?}", new_lk),
Err(_) => {}
}
let data = mtx.into_inner();
assert_eq!(data.x, 21);
assert_eq!(data.y, 42);
}
}