use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ops::Deref;
use core::sync::atomic::{AtomicU8, Ordering};
pub struct Once<T: Send> {
cell: UnsafeCell<MaybeUninit<T>>,
state: AtomicU8,
}
unsafe impl<T: Send> Send for Once<T> {}
unsafe impl<T: Send> Sync for Once<T> {}
#[derive(Debug, Clone, Copy)]
pub struct OnceError;
impl<T: Send> Once<T> {
pub const fn new() -> Self {
Self {
cell: UnsafeCell::new(MaybeUninit::uninit()),
state: AtomicU8::new(0),
}
}
pub fn set(&self, value: T) -> Result<(), OnceError> {
if self
.state
.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let inner = unsafe { &mut *self.cell.get() };
inner.write(value);
let prev = self.state.fetch_add(1, Ordering::AcqRel);
assert!(prev == 1);
Ok(())
} else {
Err(OnceError)
}
}
pub fn get(&self) -> Option<&T> {
if self.state.load(Ordering::Acquire) == 2 {
let inner = unsafe { &*self.cell.get() };
Some(unsafe { inner.assume_init_ref() })
} else {
None
}
}
pub fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
match self
.state
.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
{
Ok(0) => {
let inner = unsafe { &mut *self.cell.get() };
let value = inner.write(f());
let prev = self.state.fetch_add(1, Ordering::AcqRel);
assert!(prev == 1);
value
}
Err(2) => unsafe { (*self.cell.get()).assume_init_ref() },
_ => panic!("Unexpected lock state"),
}
}
}
impl<T: Send> Deref for Once<T> {
type Target = T;
#[track_caller]
fn deref(&self) -> &Self::Target {
self.get().expect("Not initialized")
}
}
impl<T: Send> Drop for Once<T> {
fn drop(&mut self) {
if self.state.load(Ordering::Acquire) == 2 {
let inner = unsafe { &mut *self.cell.get() };
unsafe { inner.assume_init_drop() }
}
}
}
pub struct Lazy<T: Send, F: FnOnce() -> T + Clone> {
constructor: F,
once: Once<T>,
}
impl<T: Send, F: FnOnce() -> T + Clone> Lazy<T, F> {
pub const fn new(f: F) -> Self {
Self {
constructor: f,
once: Once::new(),
}
}
}
impl<T: Send, F: FnOnce() -> T + Clone> Deref for Lazy<T, F> {
type Target = T;
#[track_caller]
fn deref(&self) -> &Self::Target {
self.once.get_or_init(self.constructor.clone())
}
}