use alloc::vec::Vec;
use core::cell::UnsafeCell;
const DEFAULT_CHUNK_CAPACITY: usize = 16;
pub struct DropArena<T> {
chunks: UnsafeCell<Vec<Vec<T>>>,
chunk_capacity: usize,
}
impl<T> DropArena<T> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
chunks: UnsafeCell::new(Vec::new()),
chunk_capacity: DEFAULT_CHUNK_CAPACITY,
}
}
#[inline]
#[must_use]
pub fn with_chunk_capacity(chunk_capacity: usize) -> Self {
Self {
chunks: UnsafeCell::new(Vec::new()),
chunk_capacity: core::cmp::max(chunk_capacity, 1),
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
let chunks = unsafe { &*self.chunks.get() };
chunks.iter().map(Vec::len).sum()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
#[must_use]
pub fn chunk_count(&self) -> usize {
let chunks = unsafe { &*self.chunks.get() };
chunks.len()
}
#[allow(
clippy::mut_from_ref,
reason = "interior mutability via UnsafeCell; each call returns a disjoint slot in a chunk"
)]
pub fn alloc(&self, value: T) -> &mut T {
let chunks = unsafe { &mut *self.chunks.get() };
let needs_new_chunk = match chunks.last() {
None => true,
Some(c) => c.len() == c.capacity(),
};
if needs_new_chunk {
chunks.push(Vec::with_capacity(self.chunk_capacity));
}
let current = match chunks.last_mut() {
Some(c) => c,
None => panic!("drop arena chunk invariant violated"),
};
let len = current.len();
debug_assert!(len < current.capacity());
let slot_ptr: *mut T = unsafe { current.as_mut_ptr().add(len) };
unsafe { core::ptr::write(slot_ptr, value) };
unsafe { current.set_len(len + 1) };
unsafe { &mut *slot_ptr }
}
}
impl<T> Default for DropArena<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T: core::fmt::Debug> core::fmt::Debug for DropArena<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DropArena")
.field("len", &self.len())
.field("chunk_count", &self.chunk_count())
.field("chunk_capacity", &self.chunk_capacity)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
use alloc::sync::Arc;
#[test]
fn alloc_returns_unique_references() {
let arena = DropArena::<u32>::new();
let a = arena.alloc(1);
let b = arena.alloc(2);
assert_eq!(*a, 1);
assert_eq!(*b, 2);
assert_eq!(arena.len(), 2);
}
#[test]
fn grows_to_new_chunk_when_current_is_full() {
let arena = DropArena::<u32>::with_chunk_capacity(4);
for i in 0..4 {
let _ = arena.alloc(i);
}
assert_eq!(arena.chunk_count(), 1);
let _ = arena.alloc(99); assert_eq!(arena.chunk_count(), 2);
assert_eq!(arena.len(), 5);
}
#[test]
fn destructors_run_on_drop() {
let shared = Arc::new(0_u32);
{
let arena = DropArena::<Arc<u32>>::new();
let _ = arena.alloc(Arc::clone(&shared));
let _ = arena.alloc(Arc::clone(&shared));
assert_eq!(Arc::strong_count(&shared), 3);
}
assert_eq!(Arc::strong_count(&shared), 1);
}
#[test]
fn mutating_returned_reference_is_visible() {
let arena = DropArena::<String>::new();
let s = arena.alloc(String::from("hello"));
s.push_str(", world");
assert_eq!(s, "hello, world");
}
#[test]
fn references_remain_valid_across_chunk_growth() {
let arena = DropArena::<u32>::with_chunk_capacity(2);
let first = arena.alloc(100);
let _ = arena.alloc(200);
let _ = arena.alloc(300);
let _ = arena.alloc(400);
let _ = arena.alloc(500);
assert_eq!(*first, 100);
}
#[test]
fn with_chunk_capacity_clamps_zero_to_one() {
let arena = DropArena::<u8>::with_chunk_capacity(0);
let _ = arena.alloc(1);
let _ = arena.alloc(2);
assert_eq!(arena.len(), 2);
assert_eq!(arena.chunk_count(), 2);
}
}