#![allow(unreachable_pub, dead_code)]
use std::{
ops::{Deref, DerefMut},
sync::atomic::{AtomicBool, Ordering},
};
use tokio::sync::{Mutex, MutexGuard, TryLockError};
#[derive(Debug)]
pub struct Nutex<T: Sized> {
mutex: Mutex<Option<T>>,
is_some: AtomicBool,
}
#[derive(Debug)]
#[clippy::has_significant_drop]
#[must_use = "if unused the Nutex will immediately unlock"]
pub struct NutexGuard<'a, T: Sized> {
lock: &'a Nutex<T>,
guard: MutexGuard<'a, Option<T>>,
}
impl<'a, T: Sized> Nutex<T> {
pub fn new() -> Self {
Self::default()
}
pub async fn get(&self) -> T
where
T: Clone + Sized,
{
self.lock().await.clone()
}
pub fn blocking_get(&self) -> T
where
T: Clone + Sized,
{
self.blocking_lock().clone()
}
pub async fn safe_get(&self) -> Option<T>
where
T: Clone + Sized,
{
self.safe_lock().await.map(|g| g.clone())
}
pub fn safe_blocking_get(&self) -> Option<T>
where
T: Clone + Sized,
{
self.safe_blocking_lock().map(|g| g.clone())
}
pub async fn set(&self, val: T) {
if !self.is_some.load(Ordering::Relaxed) {
*self.mutex.lock().await = Some(val);
self.is_some.swap(true, Ordering::Relaxed);
}
}
pub fn blocking_set(&self, val: T) {
if !self.is_some.load(Ordering::Relaxed) {
*self.mutex.blocking_lock() = Some(val);
self.is_some.swap(true, Ordering::Relaxed);
}
}
pub async fn clear(&self) {
*self.mutex.lock().await = None;
self.is_some.swap(false, Ordering::Relaxed);
}
pub fn blocking_clear(&self) {
*self.mutex.blocking_lock() = None;
}
fn guard(&'a self, guard: MutexGuard<'a, Option<T>>) -> NutexGuard<'a, T> {
if self.is_some.load(Ordering::Relaxed) {
NutexGuard {
lock: self,
guard: guard,
}
} else {
panic!(
"Accessed inner Nutex value that's `None`.
If you're not sure that value is certainly set,
prefer using `.safe_lock()` or `.safe_blocking_lock()` methods instead."
)
}
}
fn guard_expect(&'a self, guard: MutexGuard<'a, Option<T>>, msg: &str) -> NutexGuard<'a, T> {
if self.is_some.load(Ordering::Relaxed) {
NutexGuard {
lock: self,
guard: guard,
}
} else {
panic!("{}", msg)
}
}
fn safe_guard(&'a self, guard: MutexGuard<'a, Option<T>>) -> Option<NutexGuard<'a, T>> {
if self.is_some.load(Ordering::Relaxed) {
Some(NutexGuard {
lock: self,
guard: guard,
})
} else {
None
}
}
pub async fn lock(&'a self) -> NutexGuard<'a, T> {
self.guard(self.mutex.lock().await)
}
pub async fn lock_expect(&'a self, msg: &str) -> NutexGuard<'a, T> {
self.guard_expect(self.mutex.lock().await, msg)
}
pub fn blocking_lock(&'a self) -> NutexGuard<'a, T> {
self.guard(self.mutex.blocking_lock())
}
pub fn blocking_lock_expect(&'a self, msg: &str) -> NutexGuard<'a, T> {
self.guard_expect(self.mutex.blocking_lock(), msg)
}
pub async fn safe_lock(&'a self) -> Option<NutexGuard<'a, T>> {
self.safe_guard(self.mutex.lock().await)
}
pub fn safe_blocking_lock(&'a self) -> Option<NutexGuard<'a, T>> {
self.safe_guard(self.mutex.blocking_lock())
}
pub fn try_lock(&'a self) -> Result<NutexGuard<'a, T>, TryLockError> {
self.mutex.try_lock().map(|g| self.guard(g))
}
pub async fn lock_then<U, F: AsyncFnOnce(NutexGuard<'a, T>) -> U>(&'a self, f: F) -> Option<U> {
if self.is_some() {
Some(f(self.guard(self.mutex.lock().await)).await)
} else {
None
}
}
pub fn blocking_lock_then<U, F: FnOnce(NutexGuard<'a, T>) -> U>(&'a self, f: F) -> Option<U> {
if self.is_some() {
Some(f(self.guard(self.mutex.blocking_lock())))
} else {
None
}
}
pub fn is_some(&self) -> bool {
self.is_some.load(Ordering::Relaxed)
}
pub fn is_none(&self) -> bool {
!self.is_some.load(Ordering::Relaxed)
}
pub fn into_inner(self) -> Option<T> {
self.mutex.into_inner()
}
}
impl<T: Sized> Default for Nutex<T> {
fn default() -> Self {
Self {
mutex: Mutex::new(None),
is_some: AtomicBool::new(false),
}
}
}
impl<T: Sized> From<T> for Nutex<T> {
fn from(value: T) -> Self {
Self {
mutex: Mutex::new(Some(value)),
is_some: AtomicBool::new(true),
}
}
}
impl<'a, T: Sized> NutexGuard<'a, T> {
pub fn nutex(&self) -> &'a Nutex<T> {
self.lock
}
}
impl<'a, T: Sized> Deref for NutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guard.as_ref().expect("Value must exist in NutexGuard")
}
}
impl<'a, T: Sized> DerefMut for NutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.guard.as_mut().expect("Value must exist in NutexGuard")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn access_test() {
let nutex = Nutex::from(String::from("foo"));
assert_eq!(String::from("foo"), *nutex.blocking_lock())
}
#[test]
fn mutation_test() {
let nutex: Nutex<String> = Nutex::new();
nutex.blocking_set(String::from("bar"));
assert_eq!(String::from("bar"), *nutex.blocking_lock())
}
#[test]
fn clear_test() {
let nutex: Nutex<String> = Nutex::new();
nutex.blocking_set(String::from("bar"));
assert_eq!(String::from("bar"), *nutex.blocking_lock());
nutex.blocking_clear();
assert!(nutex.safe_blocking_lock().is_none());
}
#[test]
fn closure_test() {
let nutex = Nutex::from(String::from("foo"));
nutex.blocking_lock_then(|mut g| {
println!("{}", *g);
assert_eq!(String::from("foo"), *g);
*g = String::from("bar");
println!("{}", *g);
assert_eq!(String::from("bar"), *g);
});
let none_nutex: Nutex<u8> = Nutex::new();
none_nutex.blocking_lock_then(|g| {
println!("{}", *g);
});
}
}