use std::cell::OnceCell;
use std::sync::{Mutex, MutexGuard, PoisonError, TryLockError};
pub struct Global<T> {
value: Mutex<OnceCell<T>>,
init_fn: fn() -> T,
}
impl<T> Global<T> {
pub const fn new(init_fn: fn() -> T) -> Self {
Self {
value: Mutex::new(OnceCell::new()),
init_fn,
}
}
pub const fn default() -> Self
where
T: Default,
{
Self::new(T::default)
}
pub fn lock(&self) -> GlobalGuard<'_, T> {
let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner);
guard.get_or_init(self.init_fn);
unsafe { GlobalGuard::new_unchecked(guard) }
}
pub fn try_lock(&self) -> Result<GlobalGuard<'_, T>, GlobalLockError<'_, T>> {
fn init<'mutex: 'cell, 'cell, T>(
g: MutexGuard<'mutex, OnceCell<T>>,
init_fn: fn() -> T,
) -> Result<GlobalGuard<'cell, T>, GlobalLockError<'cell, T>> {
std::panic::catch_unwind(|| g.get_or_init(init_fn))
.map_err(|_| GlobalLockError::InitFailed)?;
Ok(unsafe { GlobalGuard::new_unchecked(g) })
}
match self.value.try_lock() {
Ok(guard) => init(guard, self.init_fn),
Err(TryLockError::WouldBlock) => Err(GlobalLockError::WouldBlock),
Err(TryLockError::Poisoned(x)) => {
let circumvent = init(x.into_inner(), self.init_fn)?;
Err(GlobalLockError::Poisoned { circumvent })
}
}
}
}
mod global_guard {
use std::ops::{Deref, DerefMut};
use super::*;
pub struct GlobalGuard<'a, T> {
mutex_guard: MutexGuard<'a, OnceCell<T>>,
}
impl<'a, T> GlobalGuard<'a, T> {
pub(super) fn new(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Option<Self> {
match mutex_guard.get() {
Some(_) => Some(Self { mutex_guard }),
_ => None,
}
}
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Self {
crate::strict_assert!(
mutex_guard.get().is_some(),
"safety precondition violated: cell not initialized"
);
unsafe { Self::new(mutex_guard).unwrap_unchecked() }
}
}
impl<T> Deref for GlobalGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.mutex_guard.get().unwrap_unchecked() }
}
}
impl<T> DerefMut for GlobalGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.mutex_guard.get_mut().unwrap_unchecked() }
}
}
}
pub use global_guard::GlobalGuard;
pub enum GlobalLockError<'a, T> {
WouldBlock,
Poisoned { circumvent: GlobalGuard<'a, T> },
InitFailed,
}
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod tests {
use std::collections::HashMap;
use super::*;
static MAP: Global<HashMap<i32, &'static str>> = Global::default();
static VEC: Global<Vec<i32>> = Global::new(|| vec![1, 2, 3]);
static FAILED: Global<()> = Global::new(|| panic!("failed"));
static POISON: Global<i32> = Global::new(|| 36);
#[test]
fn test_global_map() {
{
let mut map = MAP.lock();
map.insert(2, "two");
map.insert(3, "three");
}
{
let mut map = MAP.lock();
map.insert(1, "one");
}
let map = MAP.lock();
assert_eq!(map.len(), 3);
assert_eq!(map.get(&1), Some(&"one"));
assert_eq!(map.get(&2), Some(&"two"));
assert_eq!(map.get(&3), Some(&"three"));
}
#[test]
fn test_global_vec() {
{
let mut vec = VEC.lock();
vec.push(4);
}
let vec = VEC.lock();
assert_eq!(*vec, &[1, 2, 3, 4]);
}
#[test]
#[cfg_attr(
all(target_family = "wasm", not(target_feature = "atomics")),
ignore = "Single-threaded Wasm semantics differ"
)]
fn test_global_would_block() {
let vec = VEC.lock();
let vec2 = VEC.try_lock();
assert!(matches!(vec2, Err(GlobalLockError::WouldBlock)));
}
#[test]
fn test_global_init_failed() {
let guard = FAILED.try_lock();
assert!(matches!(guard, Err(GlobalLockError::InitFailed)));
let guard = FAILED.try_lock();
assert!(matches!(guard, Err(GlobalLockError::InitFailed)));
}
#[test]
fn test_global_poison() {
let result = std::panic::catch_unwind(|| {
let guard = POISON.lock();
panic!("poison injection");
});
assert!(result.is_err());
let guard = POISON.try_lock();
let Err(GlobalLockError::Poisoned { circumvent }) = guard else {
panic!("expected GlobalLockError::Poisoned");
};
assert_eq!(*circumvent, 36);
}
}