use bumpalo::Bump;
#[derive(Debug, Default)]
pub struct Arena {
bump: Bump,
}
impl Arena {
#[must_use]
pub fn new() -> Self {
Self { bump: Bump::new() }
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
bump: Bump::with_capacity(capacity),
}
}
pub fn alloc<T>(&self, value: T) -> &T {
self.bump.alloc(value)
}
pub fn alloc_str(&self, s: &str) -> &str {
self.bump.alloc_str(s)
}
pub fn alloc_slice_copy<T: Copy>(&self, slice: &[T]) -> &[T] {
self.bump.alloc_slice_copy(slice)
}
pub fn alloc_slice_fill_iter<T, I>(&self, iter: I) -> &[T]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
self.bump.alloc_slice_fill_iter(iter)
}
#[must_use]
pub fn allocated_bytes(&self) -> usize {
self.bump.allocated_bytes()
}
#[must_use]
pub fn bump(&self) -> &Bump {
&self.bump
}
pub fn reset(&mut self) {
self.bump.reset();
}
pub fn reset_with_hint(&mut self, target_capacity: usize) {
if self.bump.allocated_bytes() >= target_capacity {
self.bump.reset();
} else {
self.bump = Bump::with_capacity(target_capacity);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_arena_is_empty() {
let a = Arena::new();
let bytes = a.allocated_bytes();
let _ = bytes; }
#[test]
fn alloc_returns_reference_with_arena_lifetime() {
let a = Arena::new();
let n: &u32 = a.alloc(42u32);
assert_eq!(*n, 42);
}
#[test]
fn alloc_str_copies_into_arena() {
let a = Arena::new();
let s = a.alloc_str("hello");
assert_eq!(s, "hello");
}
#[test]
fn alloc_slice_copy_preserves_contents() {
let a = Arena::new();
let slice = a.alloc_slice_copy(&[1u32, 2, 3, 4, 5]);
assert_eq!(slice, &[1, 2, 3, 4, 5]);
}
#[test]
fn alloc_slice_fill_iter_handles_known_length() {
let a = Arena::new();
let slice = a.alloc_slice_fill_iter([10u32, 20, 30]);
assert_eq!(slice, &[10, 20, 30]);
}
#[test]
fn with_capacity_preallocates_some_chunk() {
let a = Arena::with_capacity(4096);
assert!(a.allocated_bytes() > 0);
}
#[test]
fn many_small_allocations_share_arena() {
let a = Arena::new();
let pointers: Vec<&u32> = (0..1000u32).map(|i| a.alloc(i)).collect();
for (i, p) in pointers.iter().enumerate() {
let expected = u32::try_from(i).expect("loop bound fits in u32");
assert_eq!(**p, expected);
}
}
#[test]
fn reset_with_hint_grows_when_target_exceeds_current_capacity() {
let mut a = Arena::with_capacity(4096);
let before = a.allocated_bytes();
let target = before.saturating_mul(10).max(64 * 1024);
a.reset_with_hint(target);
let after = a.allocated_bytes();
assert!(
after >= target,
"capacity must grow to at least target (target={target}, after={after})"
);
let v: &u32 = a.alloc(7u32);
assert_eq!(*v, 7);
}
#[test]
fn reset_with_hint_is_a_plain_reset_when_target_already_met() {
let mut a = Arena::with_capacity(64 * 1024);
for i in 0..256u32 {
let _ = a.alloc(i);
}
let before = a.allocated_bytes();
a.reset_with_hint(1024);
let after = a.allocated_bytes();
assert_eq!(after, before, "small-target hint must not shrink the arena");
}
#[test]
fn reset_drops_allocations_but_keeps_capacity() {
let mut a = Arena::with_capacity(4096);
for i in 0..256u32 {
let _ = a.alloc(i);
let _ = a.alloc_str("filler");
}
let before = a.allocated_bytes();
assert!(before > 0, "fill loop must have allocated something");
a.reset();
let after = a.allocated_bytes();
assert!(
after >= before / 2,
"reset should retain at least half the previous capacity (before={before}, after={after})"
);
let v: &u32 = a.alloc(99u32);
assert_eq!(*v, 99);
}
}