use core::cell::Cell;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::{NonNull, drop_in_place};
pub struct TypedArena<T> {
base: NonNull<[MaybeUninit<T>]>,
offset: Cell<usize>,
#[allow(clippy::type_complexity)]
_invariant: PhantomData<(*const (), fn(T) -> T)>,
}
impl<T> TypedArena<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
base: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new_uninit_slice(capacity))) },
offset: Cell::new(0),
_invariant: PhantomData,
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc_raw(&self, value: T) -> Option<&mut T> {
if size_of::<T>() == 0 {
unsafe {
let dangling = NonNull::<T>::dangling();
dangling.as_ptr().write(value);
return Some(&mut *dangling.as_ptr());
}
}
let idx = self.offset.get();
if idx >= self.capacity() {
return None;
}
unsafe {
let beg = self.base.as_ptr().cast::<MaybeUninit<T>>();
let slot = &mut *beg.add(idx);
let r = slot.write(value);
self.offset.set(idx + 1);
Some(r)
}
}
pub fn len(&self) -> usize {
self.offset.get()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
self.base.len()
}
pub fn reset(&mut self) {
let offset = self.offset.get();
unsafe {
let start = self.base.as_ptr().cast::<T>();
for i in (0..offset).rev() {
drop_in_place(start.add(i));
}
}
self.offset.set(0);
}
}
impl<T> Drop for TypedArena<T> {
fn drop(&mut self) {
let offset = self.offset.get();
unsafe {
let start = self.base.as_ptr().cast::<T>();
for i in (0..offset).rev() {
drop_in_place(start.add(i));
}
drop(Box::from_raw(self.base.as_ptr()));
}
}
}
#[cfg(test)]
mod tests {
use core::ptr;
use super::*;
struct DropTracker<'a> {
id: u32,
order: &'a Cell<Vec<u32>>,
}
impl Drop for DropTracker<'_> {
fn drop(&mut self) {
let mut v = self.order.take();
v.push(self.id);
self.order.set(v);
}
}
#[test]
fn drop_order_reverse_allocation() {
let order = Cell::new(Vec::new());
let arena = TypedArena::<DropTracker>::new(10);
arena.alloc_raw(DropTracker { id: 1, order: &order }).unwrap();
arena.alloc_raw(DropTracker { id: 2, order: &order }).unwrap();
arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap();
drop(arena);
assert_eq!(order.take(), vec![3, 2, 1]);
}
#[test]
fn reset_drops_values_and_reuses_memory() {
let order = Cell::new(Vec::new());
let mut arena = TypedArena::<DropTracker>::new(10);
let ptr1 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 1, order: &order }).unwrap());
let _ptr2 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 2, order: &order }).unwrap());
arena.reset();
assert_eq!(order.take(), vec![2, 1]);
let ptr3 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap());
assert_eq!(ptr1, ptr3);
drop(arena);
assert_eq!(order.take(), vec![3]);
}
#[test]
fn no_double_drop_after_reset() {
struct Counter<'a>(&'a Cell<u32>);
impl Drop for Counter<'_> {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
}
}
let count = Cell::new(0u32);
let mut arena = TypedArena::<Counter>::new(10);
arena.alloc_raw(Counter(&count)).unwrap();
arena.alloc_raw(Counter(&count)).unwrap();
arena.reset();
assert_eq!(count.get(), 2);
arena.alloc_raw(Counter(&count)).unwrap();
drop(arena);
assert_eq!(count.get(), 3); }
#[test]
fn oom_does_not_advance_offset() {
let arena = TypedArena::<u64>::new(1); assert!(arena.alloc_raw(1u64).is_some());
assert_eq!(arena.len(), 1);
assert!(arena.alloc_raw(2u64).is_none());
assert_eq!(arena.len(), 1);
}
#[test]
fn zst_does_not_advance_offset() {
let arena = TypedArena::<()>::new(0);
assert!(arena.alloc_raw(()).is_some());
assert_eq!(arena.len(), 0);
assert!(arena.alloc_raw(()).is_some());
assert_eq!(arena.len(), 0);
}
#[test]
fn allocated_value_is_valid() {
let arena = TypedArena::<String>::new(1);
let s = arena.alloc_raw("hello".to_string()).unwrap();
assert_eq!(s, "hello");
s.push_str(" world");
assert_eq!(s, "hello world");
}
}