#![doc(html_root_url = "https://docs.rs/double-checked-cell/2.1.0")]
#![warn(missing_debug_implementations)]
#[cfg(feature = "parking_lot_mutex")]
extern crate parking_lot;
extern crate unreachable;
extern crate void;
use std::cell::UnsafeCell;
use std::fmt;
use std::panic::{UnwindSafe, RefUnwindSafe};
use std::sync::atomic::{AtomicBool, Ordering};
use unreachable::UncheckedOptionExt;
use void::ResultVoidExt;
pub struct DoubleCheckedCell<T> {
value: UnsafeCell<Option<T>>,
initialized: AtomicBool,
#[cfg(not(feature = "parking_lot_mutex"))]
lock: std::sync::Mutex<()>,
#[cfg(feature = "parking_lot_mutex")]
lock: parking_lot::Mutex<()>,
}
impl<T> Default for DoubleCheckedCell<T> {
fn default() -> DoubleCheckedCell<T> {
DoubleCheckedCell::new()
}
}
impl<T: fmt::Debug> fmt::Debug for DoubleCheckedCell<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("DoubleCheckedCell")
.field("value", &self.get())
.finish()
}
}
impl<T> DoubleCheckedCell<T> {
#[cfg(not(feature = "const_fn"))]
pub fn new() -> DoubleCheckedCell<T> {
DoubleCheckedCell {
value: UnsafeCell::new(None),
initialized: AtomicBool::new(false),
#[cfg(not(feature = "parking_lot_mutex"))]
lock: std::sync::Mutex::new(()),
#[cfg(feature = "parking_lot_mutex")]
lock: parking_lot::Mutex::new(()),
}
}
#[cfg(feature = "const_fn")]
pub const fn new() -> DoubleCheckedCell<T> {
DoubleCheckedCell {
value: UnsafeCell::new(None),
initialized: AtomicBool::new(false),
lock: parking_lot::Mutex::new(()),
}
}
pub fn get(&self) -> Option<&T> {
self.get_or_try_init(|| Err(())).ok()
}
pub fn get_or_init<F>(&self, init: F) -> &T
where
F: FnOnce() -> T,
{
self.get_or_try_init(|| Ok(init())).void_unwrap()
}
pub fn get_or_try_init<F, E>(&self, init: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if !self.initialized.load(Ordering::Acquire) {
#[cfg(not(feature = "parking_lot_mutex"))]
let _lock = self.lock.lock().unwrap_or_else(|poison| poison.into_inner());
#[cfg(feature = "parking_lot_mutex")]
let _lock = self.lock.lock();
if !self.initialized.load(Ordering::Relaxed) {
{
let value = unsafe { &mut *self.value.get() };
*value = Some(init()?);
}
self.initialized.store(true, Ordering::Release);
}
}
let value = unsafe { &*self.value.get() };
Ok(unsafe { value.as_ref().unchecked_unwrap() })
}
pub fn into_inner(self) -> Option<T> {
#[allow(unused_unsafe)]
unsafe { self.value.into_inner() }
}
}
impl<T> From<T> for DoubleCheckedCell<T> {
fn from(t: T) -> DoubleCheckedCell<T> {
let cell = DoubleCheckedCell::new();
cell.get_or_init(|| t);
cell
}
}
unsafe impl<T: Send + Sync> Sync for DoubleCheckedCell<T> {}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for DoubleCheckedCell<T> {}
impl<T: UnwindSafe> UnwindSafe for DoubleCheckedCell<T> {}
#[cfg(test)]
extern crate scoped_pool;
#[cfg(test)]
mod tests {
use super::*;
use std::rc::Rc;
use std::sync::atomic::AtomicUsize;
use scoped_pool::Pool;
#[test]
fn test_drop() {
let rc = Rc::new(true);
assert_eq!(Rc::strong_count(&rc), 1);
{
let cell = DoubleCheckedCell::new();
cell.get_or_init(|| rc.clone());
assert_eq!(Rc::strong_count(&rc), 2);
}
assert_eq!(Rc::strong_count(&rc), 1);
}
#[test]
fn test_threading() {
let n = AtomicUsize::new(0);
let cell = DoubleCheckedCell::new();
let pool = Pool::new(32);
pool.scoped(|scope| {
for _ in 0..1000 {
scope.execute(|| {
let value = cell.get_or_init(|| {
n.fetch_add(1, Ordering::Relaxed);
true
});
assert!(*value);
});
}
});
assert_eq!(n.load(Ordering::SeqCst), 1);
}
#[test]
fn test_sync_send() {
fn assert_sync<T: Sync>(_: T) {}
fn assert_send<T: Send>(_: T) {}
assert_sync(DoubleCheckedCell::<usize>::new());
assert_send(DoubleCheckedCell::<usize>::new());
}
#[cfg(feature = "const_fn")]
#[test]
fn test_static_cell() {
fn wrapper(v: u32) -> u32 {
static STATIC_CELL: DoubleCheckedCell<u32> = DoubleCheckedCell::new();
*STATIC_CELL.get_or_init(|| v)
}
assert_eq!(wrapper(1), 1);
assert_eq!(wrapper(2), 1);
assert_eq!(wrapper(3), 1);
}
struct _AssertObjectSafe(Box<DoubleCheckedCell<usize>>);
}