use core::cell::Cell;
use core::future::{poll_fn, Future};
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::Poll;
pub struct OnceLock<T> {
init: AtomicBool,
data: Cell<MaybeUninit<T>>,
}
unsafe impl<T> Sync for OnceLock<T> {}
impl<T> OnceLock<T> {
pub const fn new() -> Self {
Self {
init: AtomicBool::new(false),
data: Cell::new(MaybeUninit::zeroed()),
}
}
pub fn get(&self) -> impl Future<Output = &T> {
poll_fn(|cx| match self.try_get() {
Some(data) => Poll::Ready(data),
None => {
cx.waker().wake_by_ref();
Poll::Pending
}
})
}
pub fn try_get(&self) -> Option<&T> {
if self.init.load(Ordering::Relaxed) {
Some(unsafe { self.get_ref_unchecked() })
} else {
None
}
}
pub fn init(&self, value: T) -> Result<(), T> {
critical_section::with(|_| {
if !self.init.load(Ordering::Relaxed) {
self.data.set(MaybeUninit::new(value));
self.init.store(true, Ordering::Relaxed);
Ok(())
} else {
Err(value)
}
})
}
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
critical_section::with(|_| {
if !self.init.load(Ordering::Relaxed) {
self.data.set(MaybeUninit::new(f()));
self.init.store(true, Ordering::Relaxed);
}
});
unsafe { self.get_ref_unchecked() }
}
pub fn into_inner(self) -> Option<T> {
if self.init.load(Ordering::Relaxed) {
Some(unsafe { self.data.into_inner().assume_init() })
} else {
None
}
}
pub fn take(&mut self) -> Option<T> {
critical_section::with(|_| {
if self.init.load(Ordering::Relaxed) {
let val = unsafe { self.data.replace(MaybeUninit::zeroed()).assume_init() };
self.init.store(false, Ordering::Relaxed);
Some(val)
} else {
None
}
})
}
pub fn is_set(&self) -> bool {
self.init.load(Ordering::Relaxed)
}
unsafe fn get_ref_unchecked(&self) -> &T {
(*self.data.as_ptr()).assume_init_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn once_lock() {
let lock = OnceLock::new();
assert_eq!(lock.try_get(), None);
assert_eq!(lock.is_set(), false);
let v = 42;
assert_eq!(lock.init(v), Ok(()));
assert_eq!(lock.is_set(), true);
assert_eq!(lock.try_get(), Some(&v));
assert_eq!(lock.try_get(), Some(&v));
let v = 43;
assert_eq!(lock.init(v), Err(v));
assert_eq!(lock.is_set(), true);
assert_eq!(lock.try_get(), Some(&42));
}
#[test]
fn once_lock_get_or_init() {
let lock = OnceLock::new();
assert_eq!(lock.try_get(), None);
assert_eq!(lock.is_set(), false);
let v = lock.get_or_init(|| 42);
assert_eq!(v, &42);
assert_eq!(lock.is_set(), true);
assert_eq!(lock.try_get(), Some(&42));
let v = lock.get_or_init(|| 43);
assert_eq!(v, &42);
assert_eq!(lock.is_set(), true);
assert_eq!(lock.try_get(), Some(&42));
}
#[test]
fn once_lock_static() {
static LOCK: OnceLock<i32> = OnceLock::new();
let v: &'static i32 = LOCK.get_or_init(|| 42);
assert_eq!(v, &42);
let v: &'static i32 = LOCK.get_or_init(|| 43);
assert_eq!(v, &42);
}
#[futures_test::test]
async fn once_lock_async() {
static LOCK: OnceLock<i32> = OnceLock::new();
assert!(LOCK.init(42).is_ok());
let v: &'static i32 = LOCK.get().await;
assert_eq!(v, &42);
}
#[test]
fn once_lock_into_inner() {
let lock: OnceLock<i32> = OnceLock::new();
let v = lock.get_or_init(|| 42);
assert_eq!(v, &42);
assert_eq!(lock.into_inner(), Some(42));
}
#[test]
fn once_lock_take_init() {
let mut lock: OnceLock<i32> = OnceLock::new();
assert_eq!(lock.get_or_init(|| 42), &42);
assert_eq!(lock.is_set(), true);
assert_eq!(lock.take(), Some(42));
assert_eq!(lock.is_set(), false);
assert_eq!(lock.get_or_init(|| 43), &43);
assert_eq!(lock.is_set(), true);
}
}