use alloc::boxed::Box;
use core::{
ptr,
sync::atomic::{AtomicPtr, Ordering},
};
pub struct AtomicSlot<T> {
ptr: AtomicPtr<T>,
}
unsafe impl<T: Send> Send for AtomicSlot<T> {}
unsafe impl<T: Send> Sync for AtomicSlot<T> {}
impl<T> AtomicSlot<T> {
#[must_use]
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
pub fn put(&self, val: T) {
let new_ptr = Box::into_raw(Box::new(val));
let old_ptr = self.ptr.swap(new_ptr, Ordering::AcqRel);
if !old_ptr.is_null() {
drop(unsafe { Box::from_raw(old_ptr) });
}
}
pub fn take(&self) -> Option<T> {
let ptr = self.ptr.swap(ptr::null_mut(), Ordering::AcqRel);
if ptr.is_null() {
None
} else {
Some(*unsafe { Box::from_raw(ptr) })
}
}
}
impl<T> Default for AtomicSlot<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> core::fmt::Debug for AtomicSlot<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AtomicSlot").finish_non_exhaustive()
}
}
impl<T> Drop for AtomicSlot<T> {
fn drop(&mut self) {
let ptr = *self.ptr.get_mut();
if !ptr.is_null() {
drop(unsafe { Box::from_raw(ptr) });
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_slot_returns_none() {
let slot: AtomicSlot<u64> = AtomicSlot::new();
assert_eq!(slot.take(), None);
}
#[test]
fn put_then_take() {
let slot = AtomicSlot::new();
slot.put(42u64);
assert_eq!(slot.take(), Some(42));
}
#[test]
fn take_empties_slot() {
let slot = AtomicSlot::new();
slot.put(42u64);
assert_eq!(slot.take(), Some(42));
assert_eq!(slot.take(), None);
}
#[test]
fn put_overwrites_previous() {
let slot = AtomicSlot::new();
slot.put(1u64);
slot.put(2);
assert_eq!(slot.take(), Some(2));
assert_eq!(slot.take(), None);
}
#[test]
fn default_is_empty() {
let slot: AtomicSlot<u64> = AtomicSlot::default();
assert_eq!(slot.take(), None);
}
#[test]
fn debug_is_opaque() {
let slot: AtomicSlot<u64> = AtomicSlot::new();
let dbg = alloc::format!("{slot:?}");
assert!(dbg.contains("AtomicSlot"));
}
#[test]
fn drop_cleans_up_occupied_slot() {
use alloc::rc::Rc;
let rc = Rc::new(());
let slot = AtomicSlot::new();
slot.put(rc.clone());
assert_eq!(Rc::strong_count(&rc), 2);
drop(slot);
assert_eq!(Rc::strong_count(&rc), 1);
}
#[test]
fn put_drops_overwritten_value() {
use alloc::rc::Rc;
let first = Rc::new(());
let second = Rc::new(());
let slot = AtomicSlot::new();
slot.put(first.clone());
assert_eq!(Rc::strong_count(&first), 2);
slot.put(second.clone());
assert_eq!(Rc::strong_count(&first), 1);
assert_eq!(Rc::strong_count(&second), 2);
}
#[test]
fn works_with_non_copy_types() {
let slot = AtomicSlot::new();
slot.put(alloc::string::String::from("hello"));
assert_eq!(slot.take(), Some(alloc::string::String::from("hello")));
}
#[test]
fn multiple_put_take_cycles() {
let slot = AtomicSlot::new();
for i in 0u64..10 {
slot.put(i);
assert_eq!(slot.take(), Some(i));
assert_eq!(slot.take(), None);
}
}
}