Hi! I got a bit bored and decided to speed run my own implementation of OnceLock<T>
I wrote a small test in the main function, and it works, although I'm not sure if it's 100% correct.
The code:
#![allow(unused)]
use std::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{AtomicU32, Ordering::*},
thread,
time::Duration,
};
const UNINIT: u32 = 0;
const INITIALIZING: u32 = 1;
const INITIALIZED: u32 = 2;
struct Once<T> {
x: UnsafeCell<MaybeUninit<T>>,
flag: AtomicU32,
}
unsafe impl<T: Sync> Sync for Once<T> {}
unsafe impl<T: Sync> Send for Once<T> {}
impl<T> Once<T> {
pub const fn new() -> Self {
Self {
x: UnsafeCell::new(MaybeUninit::uninit()),
flag: AtomicU32::new(UNINIT),
}
}
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
if let Ok(_) = self
.flag
.compare_exchange(UNINIT, INITIALIZING, Acquire, Relaxed)
{
let x = f();
unsafe {
(&mut *self.x.get()).write(x);
}
self.flag.store(INITIALIZED, Release);
atomic_wait::wake_all(&self.flag);
}
while self.flag.load(Relaxed) == INITIALIZING {
atomic_wait::wait(&self.flag, INITIALIZING);
}
/// Safety:
/// If we get to this point, it means that the value has been initialized.
/// Therefore we can safely read it and return an immutable reference to it.
unsafe { (&*self.x.get()).assume_init_ref() }
}
pub fn get(&self) -> Option<&T> {
if self.flag.load(Relaxed) != INITIALIZED {
return None;
}
/// Safety: the value is initialized, so we can safely read it.
Some(unsafe { (&*self.x.get()).assume_init_ref() })
}
}
static INT: Once<i32> = Once::new();
fn main() {
let handle = thread::spawn(|| {
let _ = INT.get_or_init(|| {
thread::sleep(Duration::from_secs(3));
42
});
});
thread::sleep(Duration::from_millis(100));
assert_eq!(INT.get(), None);
let x = INT.get_or_init(|| 0);
assert_eq!(*x, 42);
handle.join();
assert_eq!(*INT.get().unwrap(), 42);
assert_eq!(*INT.get_or_init(|| 43), 42);
}
Thanks in advance!