use alloc::boxed::Box;
use core::ptr;
#[cfg(target_has_atomic = "ptr")]
use core::sync::atomic::{AtomicPtr, Ordering};
#[cfg(not(target_has_atomic = "ptr"))]
use portable_atomic::{AtomicPtr, Ordering};
pub struct OnceBox<T> {
ptr: AtomicPtr<T>,
}
unsafe impl<T: Send + Sync> Sync for OnceBox<T> {}
unsafe impl<T: Send> Send for OnceBox<T> {}
impl<T> OnceBox<T> {
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
pub fn get_or_init(&self, init: impl FnOnce() -> Box<T>) -> &T {
let existing = self.ptr.load(Ordering::Acquire);
let p = if existing.is_null() {
let new = Box::into_raw(init());
match self.ptr.compare_exchange(
ptr::null_mut(),
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => new,
Err(winner) => {
drop(unsafe { Box::from_raw(new) });
winner
}
}
} else {
existing
};
unsafe { &*p }
}
}
impl<T> Default for OnceBox<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Drop for OnceBox<T> {
fn drop(&mut self) {
let p = *self.ptr.get_mut();
if !p.is_null() {
drop(unsafe { Box::from_raw(p) });
}
}
}
#[cfg(test)]
mod tests {
use super::OnceBox;
use alloc::boxed::Box;
#[test]
fn init_runs_once() {
static B: OnceBox<[u32; 4]> = OnceBox::new();
let a = B.get_or_init(|| Box::new([1, 2, 3, 4]));
let b = B.get_or_init(|| Box::new([9, 9, 9, 9]));
assert_eq!(a, &[1, 2, 3, 4]);
assert!(core::ptr::eq(a, b));
}
#[cfg(feature = "std")]
#[test]
fn concurrent_init_is_sound() {
use alloc::sync::Arc;
use alloc::vec::Vec;
let cell = Arc::new(OnceBox::<u64>::new());
let handles: Vec<_> = (0..4u64)
.map(|i| {
let c = cell.clone();
std::thread::spawn(move || *c.get_or_init(|| Box::new(0xABCD_0000 + i)))
})
.collect();
let seen: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(seen.iter().all(|&v| v == seen[0]));
assert_eq!(seen[0] & 0xFFFF_0000, 0xABCD_0000);
}
}