//! A typed arena allocator implementation with no unsafe code or interior //! mutability. //! //! API inspired by the [`typed_arena`] crate. //! //! [`typed_arena`]: https://docs.rs/typed-arena/latest/typed_arena/ use std::{cell::Cell, mem}; use loaned::{drop, LoanedMut}; pub struct Arena<'t, T> { cursor: &'t mut [Option], chunks: Vec]>>>, capacity: usize, } impl<'t, T> Arena<'t, T> { pub fn with_capacity(capacity: usize) -> Self { let capacity = capacity.max(1); let (cursor, chunk) = Self::new_chunk(capacity); Arena { cursor, chunks: vec![chunk], capacity, } } fn new_chunk(capacity: usize) -> (&'t mut [Option], LoanedMut<'t, Box<[Option]>>) { let mut chunk = Vec::with_capacity(capacity); chunk.resize_with(capacity, || None); let chunk = chunk.into_boxed_slice(); let (cursor, chunk) = LoanedMut::loan(chunk); (cursor, chunk) } pub fn alloc(&mut self, value: T) -> &'t mut T { if self.cursor.is_empty() { self.capacity *= 2; let (cursor, chunk) = Self::new_chunk(self.capacity); self.cursor = cursor; self.chunks.push(chunk); } let cursor = mem::replace(&mut self.cursor, &mut []); let (slot, cursor) = cursor.split_first_mut().unwrap(); self.cursor = cursor; *slot = Some(value); let Some(slot) = slot else { unreachable!() }; slot } pub fn into_inner(self) -> LoanedMut<'t, Vec]>>> { self.chunks.into() } } #[cfg_attr(test, test)] fn main() { let mut arena = Arena::with_capacity(1); struct CycleParticipant<'a> { name: &'static str, next: Cell>>, } impl<'a> CycleParticipant<'a> { fn next(&self) -> &'a CycleParticipant<'a> { self.next.get().unwrap() } } let a = arena.alloc(CycleParticipant { name: "a", next: Cell::new(None), }); let b = arena.alloc(CycleParticipant { name: "b", next: Cell::new(None), }); a.next.set(Some(b)); b.next.set(Some(a)); print_assert_eq!(a.name, "a"); print_assert_eq!(a.next().name, "b"); print_assert_eq!(a.next().next().name, "a"); drop!(arena.into_inner()); } #[macro_export] macro_rules! print_assert_eq { ($x:expr, $y:expr) => { println!("{} = {:?}", stringify!($x), $x); assert_eq!($x, $y); }; }