use core::cell::UnsafeCell;
use core::convert::TryFrom;
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, MaybeUninit};
use core::ptr;
use crate::state::{AtomicOnceState, OnceState, TryBlockError, Waiter};
use crate::{Internal, POISON_PANIC_MSG};
pub trait Block: Default + Internal {
fn block(state: &AtomicOnceState);
fn unblock(waiter: Waiter);
}
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: Sync {}
impl<T, B> OnceCell<T, B> {
#[inline]
pub const fn new() -> Self {
Self {
state: AtomicOnceState::new(),
inner: UnsafeCell::new(MaybeUninit::uninit()),
_marker: PhantomData,
}
}
#[inline]
pub const fn initialized(value: T) -> Self {
Self {
state: AtomicOnceState::ready(),
inner: UnsafeCell::new(MaybeUninit::new(value)),
_marker: PhantomData,
}
}
#[inline]
pub fn into_inner(self) -> Option<T> {
let res = match self.state.load().expect(POISON_PANIC_MSG) {
OnceState::Ready => Some(unsafe { self.read_unchecked() }),
_ => None,
};
mem::forget(self);
res
}
#[inline]
pub fn is_initialized(&self) -> bool {
self.state.load() == Ok(OnceState::Ready)
}
#[inline]
pub fn is_poisoned(&self) -> bool {
self.state.load().is_err()
}
#[inline]
pub unsafe fn get_unchecked(&self) -> &T {
let inner = &*self.inner.get();
&*inner.as_ptr()
}
#[inline]
unsafe fn read_unchecked(&self) -> T {
ptr::read(self.get_unchecked())
}
}
impl<T, B: Block> OnceCell<T, B> {
#[inline]
pub fn init_once(&self, func: impl FnOnce() -> T) {
if self.is_initialized() {
return;
}
if let Err(TryBlockError::WouldBlock(_)) = self.try_init_inner(func) {
B::block(&self.state);
}
}
#[inline]
pub fn try_init_once(&self, func: impl FnOnce() -> T) -> Result<(), TryInitError> {
if self.is_initialized() {
return Err(TryInitError::AlreadyInit);
}
self.try_init_inner(func)?;
Ok(())
}
#[inline]
pub fn get(&self) -> Option<&T> {
match self.state.load().expect(POISON_PANIC_MSG) {
OnceState::Ready => Some(unsafe { self.get_unchecked() }),
OnceState::WouldBlock(_) => {
B::block(&self.state);
Some(unsafe { self.get_unchecked() })
}
OnceState::Uninit => None,
}
}
#[inline]
pub fn try_get(&self) -> Result<&T, TryGetError> {
match self.state.load().expect(POISON_PANIC_MSG) {
OnceState::Ready => Ok(unsafe { self.get_unchecked() }),
OnceState::Uninit => Err(TryGetError::Uninit),
OnceState::WouldBlock(_) => Err(TryGetError::WouldBlock),
}
}
#[inline]
pub fn get_or_init(&self, func: impl FnOnce() -> T) -> &T {
if let OnceState::Ready = self.state.load().expect(POISON_PANIC_MSG) {
return unsafe { self.get_unchecked() };
}
match self.try_init_inner(func) {
Ok(inner) => inner,
Err(TryBlockError::AlreadyInit) => unsafe { self.get_unchecked() },
Err(TryBlockError::WouldBlock(_)) => {
B::block(&self.state);
unsafe { self.get_unchecked() }
}
}
}
#[inline]
pub fn try_get_or_init(&self, func: impl FnOnce() -> T) -> Result<&T, WouldBlockError> {
if self.is_initialized() {
return Ok(unsafe { self.get_unchecked() });
}
Ok(self.try_init_inner(func)?)
}
#[inline(never)]
#[cold]
fn try_init_inner(&self, func: impl FnOnce() -> T) -> Result<&T, TryBlockError> {
let guard = PanicGuard::<B>::try_from(&self.state)?;
unsafe {
let inner = &mut *self.inner.get();
inner.as_mut_ptr().write(func());
}
guard.disarm();
Ok(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 {
let mut debug = f.debug_struct("OnceCell");
if self.is_initialized() {
debug.field("initialized", &true).field("inner", unsafe { self.get_unchecked() });
} else {
debug.field("initialized", &false);
}
debug.finish()
}
}
impl<T, B> Drop for OnceCell<T, B> {
#[inline]
fn drop(&mut self) {
if self.is_initialized() {
unsafe { mem::drop(self.read_unchecked()) }
}
}
}
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,
}
}
}
#[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),
}
}
}
#[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(()),
}
}
}
#[derive(Debug)]
struct PanicGuard<'a, B: Block> {
has_panicked: bool,
state: &'a AtomicOnceState,
_marker: PhantomData<B>,
}
impl<'a, B: Block> PanicGuard<'a, B> {
#[inline]
fn disarm(mut self) {
self.has_panicked = false;
mem::drop(self);
}
}
impl<'a, B: Block> TryFrom<&'a AtomicOnceState> for PanicGuard<'a, B> {
type Error = TryBlockError;
#[inline]
fn try_from(state: &'a AtomicOnceState) -> Result<Self, Self::Error> {
state.try_block()?;
Ok(Self { has_panicked: true, state, _marker: PhantomData })
}
}
impl<B: Block> Drop for PanicGuard<'_, B> {
#[inline]
fn drop(&mut self) {
let waiters =
if self.has_panicked { self.state.swap_poisoned() } else { self.state.swap_ready() };
B::unblock(waiters);
}
}