#[cfg(feature = "nightly")]
use core::alloc::Allocator;
use core::alloc::Layout;
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::mem::{self, needs_drop, size_of};
use core::ptr::{self, drop_in_place};
use crate::{BumpArena, UninitAllocator};
#[cfg(not(feature = "nightly"))]
#[derive(Debug)]
pub struct TypedArena<'a, T, A: UninitAllocator = BumpArena> {
allocator: &'a A,
allocations: UnsafeCell<Vec<*mut T>>,
_marker: PhantomData<*const ()>,
}
#[cfg(feature = "nightly")]
#[derive(Debug)]
pub struct TypedArena<'a, T, A = BumpArena>
where
A: UninitAllocator,
&'a A: Allocator,
{
allocator: &'a A,
allocations: UnsafeCell<Vec<*mut T, &'a A>>,
_marker: PhantomData<*const ()>,
}
macro_rules! impl_typed_arena_methods {
() => {
pub fn alloc(&self, value: T) -> &mut T {
self.alloc_impl(value).expect("TypedArena allocation failed")
}
pub fn try_alloc(&self, value: T) -> Option<&mut T> {
self.alloc_impl(value)
}
#[allow(clippy::mut_from_ref)]
fn alloc_impl(&self, value: T) -> Option<&mut T> {
if size_of::<T>() == 0 {
unsafe {
let dangling = ptr::NonNull::<T>::dangling();
if needs_drop::<T>() {
let allocs = &mut *self.allocations.get();
allocs.try_reserve(1).ok()?;
allocs.push(dangling.as_ptr());
}
dangling.as_ptr().write(value);
return Some(&mut *dangling.as_ptr());
}
}
unsafe {
(*self.allocations.get()).try_reserve(1).ok()?;
}
let layout = Layout::new::<T>();
let slice = self.allocator.try_alloc_uninit(layout)?;
let ptr = slice.as_mut_ptr().cast::<T>();
unsafe {
(*self.allocations.get()).push(ptr);
ptr.write(value);
Some(&mut *ptr)
}
}
pub fn allocator(&self) -> &A {
self.allocator
}
pub fn len(&self) -> usize {
unsafe { (*self.allocations.get()).len() }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn drain(self) -> DrainIter<'a, T, A> {
let this = mem::ManuallyDrop::new(self);
let allocations = unsafe { ptr::read(this.allocations.get()) };
DrainIter { pointers: allocations.into_iter(), _allocator: this.allocator }
}
pub fn reset(&mut self) {
let allocs = self.allocations.get_mut();
while let Some(ptr) = allocs.pop() {
unsafe {
drop_in_place(ptr);
}
}
}
};
}
#[cfg(not(feature = "nightly"))]
impl<'a, T, A: UninitAllocator> TypedArena<'a, T, A> {
pub fn new_in(allocator: &'a A) -> Self {
Self { allocator, allocations: UnsafeCell::new(Vec::new()), _marker: PhantomData }
}
impl_typed_arena_methods!();
}
#[cfg(feature = "nightly")]
impl<'a, T, A> TypedArena<'a, T, A>
where
A: UninitAllocator,
&'a A: Allocator,
{
pub fn new_in(allocator: &'a A) -> Self {
Self {
allocator,
allocations: UnsafeCell::new(Vec::new_in(allocator)),
_marker: PhantomData,
}
}
impl_typed_arena_methods!();
}
#[cfg(not(feature = "nightly"))]
impl<T, A: UninitAllocator> Drop for TypedArena<'_, T, A> {
fn drop(&mut self) {
self.reset();
}
}
#[cfg(feature = "nightly")]
impl<'a, T, A> Drop for TypedArena<'a, T, A>
where
A: UninitAllocator,
&'a A: Allocator,
{
fn drop(&mut self) {
self.reset();
}
}
#[cfg(not(feature = "nightly"))]
pub struct DrainIter<'a, T, A: UninitAllocator = BumpArena> {
pointers: std::vec::IntoIter<*mut T>,
_allocator: &'a A,
}
#[cfg(feature = "nightly")]
pub struct DrainIter<'a, T, A = BumpArena>
where
A: UninitAllocator,
&'a A: Allocator,
{
pointers: std::vec::IntoIter<*mut T, &'a A>,
_allocator: &'a A,
}
macro_rules! impl_drain_iter {
($($bounds:tt)*) => {
impl<'a, T, A> Iterator for DrainIter<'a, T, A>
where
A: UninitAllocator,
$($bounds)*
{
type Item = T;
fn next(&mut self) -> Option<T> {
let ptr = self.pointers.next()?;
Some(unsafe { ptr::read(ptr) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.pointers.size_hint()
}
}
impl<'a, T, A> ExactSizeIterator for DrainIter<'a, T, A>
where
A: UninitAllocator,
$($bounds)*
{
fn len(&self) -> usize {
self.pointers.len()
}
}
impl<'a, T, A> core::iter::FusedIterator for DrainIter<'a, T, A>
where
A: UninitAllocator,
$($bounds)*
{
}
impl<'a, T, A> Drop for DrainIter<'a, T, A>
where
A: UninitAllocator,
$($bounds)*
{
fn drop(&mut self) {
let remaining = self.pointers.as_slice();
for &ptr in remaining.iter().rev() {
unsafe {
drop_in_place(ptr);
}
}
}
}
};
}
#[cfg(not(feature = "nightly"))]
impl_drain_iter!();
#[cfg(feature = "nightly")]
impl_drain_iter!(&'a A: Allocator);
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::BumpArena;
#[test]
fn zst_with_drop_is_dropped_when_arena_drops() {
static DROPS: AtomicUsize = AtomicUsize::new(0);
struct Zst;
impl Drop for Zst {
fn drop(&mut self) {
DROPS.fetch_add(1, Ordering::Relaxed);
}
}
DROPS.store(0, Ordering::Relaxed);
let bump = BumpArena::new(128);
{
let arena = TypedArena::<Zst, _>::new_in(&bump);
assert!(arena.try_alloc(Zst).is_some());
assert!(arena.try_alloc(Zst).is_some());
assert_eq!(arena.len(), 2);
}
assert_eq!(DROPS.load(Ordering::Relaxed), 2);
}
#[test]
fn reset_removes_pointer_before_dropping_value() {
static DROPS: AtomicUsize = AtomicUsize::new(0);
struct PanicOnFirstDrop(u8);
impl Drop for PanicOnFirstDrop {
fn drop(&mut self) {
let _ = self.0;
assert!(DROPS.fetch_add(1, Ordering::Relaxed) != 0, "drop panic");
}
}
DROPS.store(0, Ordering::Relaxed);
let bump = BumpArena::new(128);
let mut arena = TypedArena::<PanicOnFirstDrop, _>::new_in(&bump);
arena.try_alloc(PanicOnFirstDrop(1)).unwrap();
arena.try_alloc(PanicOnFirstDrop(2)).unwrap();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| arena.reset()));
assert!(result.is_err());
assert_eq!(DROPS.load(Ordering::Relaxed), 1);
assert_eq!(arena.len(), 1);
drop(arena);
assert_eq!(DROPS.load(Ordering::Relaxed), 2);
}
#[test]
fn reset_does_not_rewind_the_backing_allocator() {
let bump = BumpArena::new(128);
let mut arena = TypedArena::<u64, _>::new_in(&bump);
assert!(arena.try_alloc(1).is_some());
let used = bump.used();
arena.reset();
assert_eq!(bump.used(), used);
assert_eq!(arena.len(), 0);
}
#[test]
fn typed_arena_defaults_to_bump_arena_backing() {
let bump = BumpArena::new(128);
let arena = TypedArena::<u64>::new_in(&bump);
assert_eq!(*arena.try_alloc(42).unwrap(), 42);
}
#[cfg(feature = "nightly")]
#[test]
fn default_bump_arena_tracking_uses_the_bump_allocator() {
let bump = BumpArena::new(128);
let arena = TypedArena::<u64>::new_in(&bump);
arena.try_alloc(42).unwrap();
assert!(bump.used() > size_of::<u64>());
}
}