use core::panic::{RefUnwindSafe, UnwindSafe};
use crate::{
CellState,
error::{ConcurrentInitialization, InitError},
loom::{AtomicU8, Ordering, UnsafeCell},
};
pub(crate) struct OnceCell<T> {
state: AtomicU8,
value: UnsafeCell<Option<T>>,
}
const INCOMPLETE: u8 = 0x0;
const RUNNING: u8 = 0x1;
const COMPLETE: u8 = 0x2;
unsafe impl<T: Sync + Send> Sync for OnceCell<T> {}
unsafe impl<T: Send> Send for OnceCell<T> {}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceCell<T> {}
impl<T: UnwindSafe> UnwindSafe for OnceCell<T> {}
impl<T> OnceCell<T> {
const_fn! {
pub(crate) const fn new() -> OnceCell<T> {
OnceCell { state: AtomicU8::new(INCOMPLETE), value: UnsafeCell::new(None) }
}
}
const_fn! {
pub(crate) const fn with_value(value: T) -> OnceCell<T> {
OnceCell { state: AtomicU8::new(COMPLETE), value: UnsafeCell::new(Some(value)) }
}
}
#[inline]
pub(crate) fn is_initialized(&self) -> bool {
self.state() == CellState::Initialized
}
#[inline]
pub(crate) fn state(&self) -> CellState {
match self.state.load(Ordering::Acquire) {
COMPLETE => CellState::Initialized,
RUNNING => CellState::Initializing,
_ => CellState::Uninitialized,
}
}
#[cold]
pub(crate) fn try_initialize<F, E>(&self, f: F) -> Result<(), InitError<E>>
where
F: FnOnce() -> Result<T, E>,
{
let mut f = Some(f);
let mut res: Result<(), E> = Ok(());
let value = &self.value;
try_initialize_inner(&self.state, &mut || {
debug_assert!(f.is_some(), "init closure called twice");
let f = unsafe { f.take().unwrap_unchecked() };
match f() {
Ok(new) => value.with_mut(|slot| unsafe {
debug_assert!((*slot).is_none());
*slot = Some(new);
true
}),
Err(err) => {
res = Err(err);
false
}
}
})?;
res.map_err(InitError::InitFunctionFailed)
}
pub(crate) unsafe fn get_unchecked(&self) -> &T {
debug_assert!(self.is_initialized());
self.value.with(|slot| unsafe { (*slot).as_ref().unwrap_unchecked() })
}
pub(crate) fn get_mut(&mut self) -> Option<&mut T> {
self.value.with_mut(|slot| unsafe { (*slot).as_mut() })
}
pub(crate) fn into_inner(self) -> Option<T> {
self.value.into_inner()
}
}
struct Guard<'a> {
state: &'a AtomicU8,
new_state: u8,
}
impl<'a> Drop for Guard<'a> {
fn drop(&mut self) {
self.state.store(self.new_state, Ordering::Release);
}
}
#[inline(never)]
fn try_initialize_inner(
state: &AtomicU8,
init: &mut dyn FnMut() -> bool,
) -> Result<(), ConcurrentInitialization> {
loop {
let exchange =
state.compare_exchange_weak(INCOMPLETE, RUNNING, Ordering::Acquire, Ordering::Acquire);
match exchange {
Ok(_) => {
let mut guard = Guard { state, new_state: INCOMPLETE };
if init() {
guard.new_state = COMPLETE;
}
return Ok(());
}
Err(COMPLETE) => return Ok(()),
Err(RUNNING) => return Err(ConcurrentInitialization),
Err(INCOMPLETE) => (),
Err(_) => debug_assert!(false, "invalid cell state"),
}
}
}
#[cfg(not(loom))]
#[test]
fn test_size() {
use core::mem::size_of;
assert_eq!(size_of::<OnceCell<bool>>(), size_of::<bool>() + size_of::<u8>());
}