use core::{
cell::UnsafeCell,
fmt,
marker::PhantomData,
mem::{self, MaybeUninit},
ptr,
sync::atomic::Ordering,
};
use crate::{
state::{AtomicOnceState, BlockedState, OnceState, SwapState, TryBlockError},
POISON_PANIC_MSG,
};
pub trait Unblock {
unsafe fn on_unblock(state: BlockedState);
}
pub unsafe trait Block: Unblock {
fn block(state: &AtomicOnceState);
}
pub struct OnceCell<T, B> {
state: AtomicOnceState,
inner: UnsafeCell<MaybeUninit<T>>,
_marker: PhantomData<B>,
}
unsafe impl<T, B> Send for OnceCell<T, B> where T: Send {}
unsafe impl<T, B> Sync for OnceCell<T, B> where T: Send + Sync {}
impl<T, B> OnceCell<T, B> {
#[inline]
pub const fn uninit() -> Self {
Self {
state: AtomicOnceState::new(),
inner: UnsafeCell::new(MaybeUninit::uninit()),
_marker: PhantomData,
}
}
#[inline]
pub const fn new(value: T) -> Self {
Self {
state: AtomicOnceState::ready(),
inner: UnsafeCell::new(MaybeUninit::new(value)),
_marker: PhantomData,
}
}
#[inline]
pub fn into_inner(mut self) -> Option<T> {
let res = unsafe { self.take_inner(false) };
mem::forget(self);
res
}
#[inline]
pub fn is_initialized(&self) -> bool {
self.state.load(Ordering::Acquire) == Ok(OnceState::Ready)
}
#[inline]
pub fn is_poisoned(&self) -> bool {
self.state.load(Ordering::Relaxed).is_err()
}
#[inline]
pub fn try_get(&self) -> Result<&T, TryGetError> {
match self.state.load(Ordering::Acquire).expect(POISON_PANIC_MSG) {
OnceState::Ready => Ok(unsafe { self.get_unchecked() }),
OnceState::Uninit => Err(TryGetError::Uninit),
OnceState::WouldBlock(_) => Err(TryGetError::WouldBlock),
}
}
#[inline]
pub unsafe fn get_unchecked(&self) -> &T {
let inner = &*self.inner.get();
&*inner.as_ptr()
}
#[inline]
unsafe fn take_inner(&mut self, ignore_poisoning: bool) -> Option<T> {
#[allow(clippy::match_wild_err_arm)]
match self.state.load(Ordering::Relaxed) {
Err(_) if !ignore_poisoning => panic!("{}", POISON_PANIC_MSG),
Ok(OnceState::Ready) =>
{
#[allow(unused_unsafe)]
Some(unsafe { ptr::read(self.get_unchecked()) })
}
_ => None,
}
}
}
impl<T, B: Unblock> OnceCell<T, B> {
#[inline]
pub fn try_init_once(&self, func: impl FnOnce() -> T) -> Result<(), TryInitError> {
match self.state.load(Ordering::Acquire).expect(POISON_PANIC_MSG) {
OnceState::Ready => Err(TryInitError::AlreadyInit),
OnceState::WouldBlock(_) => Err(TryInitError::WouldBlock),
OnceState::Uninit => {
let mut func = Some(func);
self.try_init_inner(&mut || func.take().unwrap()())?;
Ok(())
}
}
}
#[inline(never)]
#[cold]
fn try_init_inner(&self, func: &mut dyn FnMut() -> T) -> Result<&T, TryBlockError> {
let guard = PanicGuard::<B>::try_block(&self.state)?;
unsafe {
let inner = &mut *self.inner.get();
inner.as_mut_ptr().write(func());
}
guard.disarm();
Ok(unsafe { self.get_unchecked() })
}
#[inline]
pub fn try_get_or_init(&self, func: impl FnOnce() -> T) -> Result<&T, WouldBlockError> {
match self.try_get() {
Ok(res) => Ok(res),
Err(TryGetError::WouldBlock) => Err(WouldBlockError(())),
Err(TryGetError::Uninit) => {
let mut func = Some(func);
let res = self.try_init_inner(&mut || func.take().unwrap()())?;
Ok(res)
}
}
}
}
impl<T, B: Block> OnceCell<T, B> {
#[inline]
pub fn get(&self) -> Option<&T> {
match self.try_get() {
Ok(res) => Some(res),
Err(TryGetError::WouldBlock) => {
B::block(&self.state);
Some(unsafe { self.get_unchecked() })
}
Err(TryGetError::Uninit) => None,
}
}
#[inline]
pub fn init_once(&self, func: impl FnOnce() -> T) {
if let Err(TryInitError::WouldBlock) = self.try_init_once(func) {
B::block(&self.state);
}
}
#[inline]
pub fn get_or_init(&self, func: impl FnOnce() -> T) -> &T {
match self.try_get_or_init(func) {
Ok(res) => res,
Err(_) => {
B::block(&self.state);
unsafe { self.get_unchecked() }
}
}
}
}
impl<T: fmt::Debug, B> fmt::Debug for OnceCell<T, B> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("OnceCell").field("inner", &self.try_get().ok()).finish()
}
}
impl<T, B> Drop for OnceCell<T, B> {
#[inline]
fn drop(&mut self) {
mem::drop(unsafe { self.take_inner(true) })
}
}
const UNINIT_MSG: &str = "the `OnceCell` is uninitialized";
const ALREADY_INIT_MSG: &str = "the `OnceCell` has already been initialized";
const WOULD_BLOCK_MSG: &str = "the `OnceCell` is currently being initialized";
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum TryInitError {
AlreadyInit,
WouldBlock,
}
impl fmt::Display for TryInitError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TryInitError::AlreadyInit => write!(f, "{}", ALREADY_INIT_MSG),
TryInitError::WouldBlock => write!(f, "{}", WOULD_BLOCK_MSG),
}
}
}
impl From<TryBlockError> for TryInitError {
#[inline]
fn from(err: TryBlockError) -> Self {
match err {
TryBlockError::AlreadyInit => TryInitError::AlreadyInit,
TryBlockError::WouldBlock(_) => TryInitError::WouldBlock,
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TryInitError {}
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum TryGetError {
Uninit,
WouldBlock,
}
impl fmt::Display for TryGetError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TryGetError::Uninit => write!(f, "{}", UNINIT_MSG),
TryGetError::WouldBlock => write!(f, "{}", WOULD_BLOCK_MSG),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TryGetError {}
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct WouldBlockError(());
impl fmt::Display for WouldBlockError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", WOULD_BLOCK_MSG)
}
}
impl From<TryBlockError> for WouldBlockError {
#[inline]
fn from(err: TryBlockError) -> Self {
match err {
TryBlockError::AlreadyInit => unreachable!(),
TryBlockError::WouldBlock(_) => Self(()),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WouldBlockError {}
#[derive(Debug)]
struct PanicGuard<'a, B: Unblock> {
state: &'a AtomicOnceState,
poison: bool,
_marker: PhantomData<B>,
}
impl<'a, B: Unblock> PanicGuard<'a, B> {
#[inline]
fn try_block(state: &'a AtomicOnceState) -> Result<Self, TryBlockError> {
state.try_block(Ordering::Acquire)?;
Ok(Self { state, poison: true, _marker: PhantomData })
}
#[inline]
fn disarm(mut self) {
self.poison = false;
mem::drop(self);
}
}
impl<B: Unblock> Drop for PanicGuard<'_, B> {
#[inline]
fn drop(&mut self) {
let swap = if self.poison { SwapState::Poisoned } else { SwapState::Ready };
unsafe {
let prev = self.state.unblock(swap, Ordering::AcqRel);
B::on_unblock(prev);
}
}
}