diff --git a/Cargo.lock b/Cargo.lock index d6e98bb..b801a0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "linalloc" -version = "1.1.0" +version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index ab2b6f9..8740d3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linalloc" -version = "1.1.0" +version = "1.2.0" edition = "2024" rust-version = "1.95" description = """ diff --git a/README.md b/README.md index 9a66e4b..bb8ada6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Small, fixed-capacity arena allocators for single-threaded Rust programs. You pick the capacity up front. The arena capacity never grows. -Addresses stay stable. When it is full, allocation returns `None`. +Addresses stay stable. When it is full, fallible allocation returns `None`. ## Choose an arena @@ -30,6 +30,14 @@ All arenas are `!Send` and `!Sync`. They are deliberately single-threaded. standard-library `allocator_api` implementation for `BumpArena` and `BumpArenaLazy`. +## Allocation APIs + +All arena types expose `try_*` for fallible allocation and `alloc` / `alloc_*` for the +panicking variant. The older inherent methods, `TypedArena::alloc_raw`, +`TypedArenaLazy::alloc_raw`, `BumpArena::alloc_uninit_slice`, and +`BumpArenaLazy::alloc_uninit_slice`, remain available for compatibility but are +deprecated. + ## Use a bump arena `BumpArena` gives you uninitialized bytes. You choose the layout, initialize @@ -41,7 +49,7 @@ use core::alloc::Layout; use linalloc::BumpArena; let arena = BumpArena::new(128); -let slot = arena.alloc_uninit_slice(Layout::new::()).unwrap(); +let slot = arena.try_alloc_uninit(Layout::new::()).unwrap(); let ptr = slot.as_mut_ptr().cast::(); unsafe { ptr.write(42) }; @@ -87,7 +95,7 @@ the arena is reset or dropped. use linalloc::TypedArena; let arena = TypedArena::::new(4); -let value = arena.alloc_raw("hello".to_owned()).unwrap(); +let value = arena.try_alloc("hello".to_owned()).unwrap(); value.push_str(" world"); assert_eq!(value, "hello world"); @@ -100,7 +108,7 @@ are still live: use linalloc::TypedArena; let mut arena = TypedArena::::new(1); -let value = arena.alloc_raw("held".to_owned()).unwrap(); +let value = arena.try_alloc("held".to_owned()).unwrap(); // ----- immutable borrow occurs here arena.reset(); //^^^^^^^^^^^^^ mutable borrow occurs here @@ -157,8 +165,8 @@ know whether the OS refused more committed memory. } let typed = TypedArenaLazy::::new(1); - assert!(typed.alloc_raw(1).is_some()); - assert!(typed.alloc_raw(2).is_none()); + assert!(typed.try_alloc(1).is_some()); + assert!(typed.try_alloc(2).is_none()); assert_eq!(typed.last_os_error_code(), None); } ``` diff --git a/src/bump_arena.rs b/src/bump_arena.rs index 393540d..40df6ff 100644 --- a/src/bump_arena.rs +++ b/src/bump_arena.rs @@ -32,7 +32,7 @@ use crate::UninitAllocator; /// /// // Allocate space for a `u64`. /// let layout = Layout::new::(); -/// let slice = bump.alloc_uninit_slice(layout).unwrap(); +/// let slice = bump.try_alloc_uninit(layout).unwrap(); /// let ptr = slice.as_mut_ptr().cast::(); /// unsafe { ptr.write(42) }; /// let val = unsafe { &*ptr }; @@ -68,17 +68,30 @@ impl BumpArena { } } + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies + /// `layout`, panicking if the allocation fails. + /// + /// See [`BumpArena::try_alloc_uninit`] for fallible allocation semantics. + /// + /// # Panics + /// + /// Panics if the arena does not have enough free space after accounting for + /// the requested size and alignment. + pub fn alloc_uninit(&self, layout: Layout) -> &mut [MaybeUninit] { + self.alloc_uninit_impl(layout).expect("BumpArena allocation failed") + } + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies /// `layout`. /// /// The returned memory is **logically uninitialised** -- it must be - /// initialised (e.g. with [`ptr::write`]) before any reads are + /// initialised (e.g. with [`core::ptr::write`]) before any reads are /// performed. /// /// The slice borrows the arena immutably (`&self`), so the arena /// cannot be dropped or moved while the slice is alive. The /// backing store is never resized, so non‑zero allocations remain - /// valid until the arena is dropped or [`reset`] is called. A + /// valid until the arena is dropped or [`BumpArena::reset`] is called. A /// zero‑size allocation returns a well‑aligned dangling slice and /// does not advance the bump pointer. /// @@ -86,11 +99,19 @@ impl BumpArena { /// /// `None` if the arena does not have enough free space after /// accounting for the requested size and alignment. - /// - /// [`ptr::write`]: core::ptr::write - /// [`reset`]: BumpArena::reset - #[allow(clippy::mut_from_ref)] + pub fn try_alloc_uninit(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) + } + + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies + /// `layout`. + #[deprecated(since = "1.2.0", note = "Use `BumpArena::try_alloc_uninit` instead.")] pub fn alloc_uninit_slice(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) + } + + #[allow(clippy::mut_from_ref)] + fn alloc_uninit_impl(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { let size = layout.size(); if size == 0 { let ptr = layout.dangling_ptr().as_ptr().cast::>(); @@ -157,8 +178,8 @@ impl Drop for BumpArena { // Safety: all safety invariants required by `UninitAllocator` are upheld by `BumpArena`. unsafe impl UninitAllocator for BumpArena { - fn alloc_uninit_slice(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { - self.alloc_uninit_slice(layout) + fn try_alloc_uninit(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) } } @@ -173,7 +194,7 @@ unsafe impl UninitAllocator for BumpArena { #[cfg(feature = "nightly")] unsafe impl core::alloc::Allocator for BumpArena { fn allocate(&self, layout: Layout) -> Result, core::alloc::AllocError> { - let slice = self.alloc_uninit_slice(layout).ok_or(core::alloc::AllocError)?; + let slice = self.alloc_uninit_impl(layout).ok_or(core::alloc::AllocError)?; // SAFETY: `slice` is guaranteed to be non-null and valid for `layout.size()` bytes. let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) @@ -298,7 +319,7 @@ mod tests { for align in [1, 2, 4, 8, 16] { let layout = Layout::from_size_align(3, align).unwrap(); - let slice = bump.alloc_uninit_slice(layout).unwrap(); + let slice = bump.try_alloc_uninit(layout).unwrap(); let ptr = slice.as_ptr() as usize; assert_eq!(ptr % align, 0); assert_eq!(slice.len(), 3); @@ -315,8 +336,8 @@ mod tests { #[test] fn alloc_no_overlap() { let bump = BumpArena::new(64); - let a = bump.alloc_uninit_slice(Layout::from_size_align(16, 8).unwrap()).unwrap(); - let b = bump.alloc_uninit_slice(Layout::from_size_align(8, 8).unwrap()).unwrap(); + let a = bump.try_alloc_uninit(Layout::from_size_align(16, 8).unwrap()).unwrap(); + let b = bump.try_alloc_uninit(Layout::from_size_align(8, 8).unwrap()).unwrap(); let a_start = a.as_ptr() as usize; let a_end = a_start + a.len(); @@ -330,11 +351,11 @@ mod tests { fn alloc_oom_does_not_advance() { let bump = BumpArena::new(16); let layout = Layout::from_size_align(8, 1).unwrap(); - bump.alloc_uninit_slice(layout).unwrap(); + bump.try_alloc_uninit(layout).unwrap(); let used_before = bump.used(); let too_large = Layout::from_size_align(9, 1).unwrap(); - assert!(bump.alloc_uninit_slice(too_large).is_none()); + assert!(bump.try_alloc_uninit(too_large).is_none()); assert_eq!(bump.used(), used_before); assert!(bump.used() <= bump.capacity()); } @@ -343,22 +364,22 @@ mod tests { fn reset_reuses_base() { let bump = BumpArena::new(32); let layout = Layout::from_size_align(8, 4).unwrap(); - let first = bump.alloc_uninit_slice(layout).unwrap(); + let first = bump.try_alloc_uninit(layout).unwrap(); let first_ptr = first.as_ptr() as usize; unsafe { bump.reset() }; assert_eq!(bump.used(), 0); - let second = bump.alloc_uninit_slice(layout).unwrap(); + let second = bump.try_alloc_uninit(layout).unwrap(); let second_ptr = second.as_ptr() as usize; assert_eq!(first_ptr, second_ptr); } #[test] - fn zero_capacity_rejects_nonzero_alloc_uninit_slice() { + fn zero_capacity_rejects_nonzero_alloc_uninit() { let bump = BumpArena::new(0); let layout = Layout::from_size_align(1, 1).unwrap(); - assert!(bump.alloc_uninit_slice(layout).is_none()); + assert!(bump.try_alloc_uninit(layout).is_none()); assert_eq!(bump.used(), 0); } @@ -366,7 +387,7 @@ mod tests { fn zero_size_alloc_does_not_advance() { let bump = BumpArena::new(8); let layout = Layout::from_size_align(0, 8).unwrap(); - let slice = bump.alloc_uninit_slice(layout).unwrap(); + let slice = bump.try_alloc_uninit(layout).unwrap(); assert_eq!(slice.len(), 0); assert_eq!(bump.used(), 0); } diff --git a/src/bump_arena_lazy.rs b/src/bump_arena_lazy.rs index 2b4a33a..156379b 100644 --- a/src/bump_arena_lazy.rs +++ b/src/bump_arena_lazy.rs @@ -43,7 +43,7 @@ use crate::{UninitAllocator, sys}; /// /// // Allocate space for a `u64`. /// let layout = Layout::new::(); -/// let slice = bump.alloc_uninit_slice(layout).expect("out of memory"); +/// let slice = bump.try_alloc_uninit(layout).expect("out of memory"); /// let ptr = slice.as_mut_ptr().cast::(); /// unsafe { ptr.write(42) }; /// let val = unsafe { &*ptr }; @@ -120,12 +120,25 @@ impl BumpArenaLazy { }) } + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies + /// `layout`, panicking if the allocation fails. + /// + /// See [`BumpArenaLazy::try_alloc_uninit`] for fallible allocation semantics. + /// + /// # Panics + /// + /// Panics if the arena does not have enough free space after accounting for + /// the requested size and alignment, or if a required memory commit fails. + pub fn alloc_uninit(&self, layout: Layout) -> &mut [MaybeUninit] { + self.alloc_uninit_impl(layout).expect("BumpArenaLazy allocation failed") + } + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies /// `layout`. /// /// The returned memory is **logically uninitialised** -- it must be /// initialised before any reads are performed (for example, using - /// [`ptr::write`]). + /// [`core::ptr::write`]). /// /// The slice borrows the arena immutably (`&self`), so the arena cannot /// be dropped or moved while the slice is alive. This guarantees that @@ -139,10 +152,19 @@ impl BumpArenaLazy { /// `None` if the arena does not have enough free space after accounting /// for the requested size and alignment, or if a required memory commit /// fails. - /// - /// [`ptr::write`]: core::ptr::write - #[allow(clippy::mut_from_ref)] + pub fn try_alloc_uninit(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) + } + + /// Allocates a mutable slice of [`MaybeUninit`] that satisfies + /// `layout`. + #[deprecated(since = "1.2.0", note = "Use `BumpArenaLazy::try_alloc_uninit` instead.")] pub fn alloc_uninit_slice(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) + } + + #[allow(clippy::mut_from_ref)] + fn alloc_uninit_impl(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { let size = layout.size(); if size == 0 { let ptr = layout.dangling_ptr().as_ptr().cast::>(); @@ -164,7 +186,7 @@ impl BumpArenaLazy { } if offset > self.commit.get() { - return self.alloc_uninit_slice_bump(aligned, offset, size); + return self.alloc_uninit_bump(aligned, offset, size); } self.offset.set(offset); @@ -178,11 +200,11 @@ impl BumpArenaLazy { } } - // With the code in `alloc_uninit_slice_bump()` out of the way, `alloc_uninit_slice()` compiles down to some super tight assembly. + // With the code in `alloc_uninit_bump()` out of the way, `alloc_uninit_impl()` compiles down to some super tight assembly. #[cold] #[inline(never)] #[allow(clippy::mut_from_ref)] - fn alloc_uninit_slice_bump( + fn alloc_uninit_bump( &self, aligned: usize, offset: usize, @@ -278,8 +300,8 @@ impl Drop for BumpArenaLazy { // Safety: all safety invariants required by `UninitAllocator` are upheld by `BumpArenaLazy`. unsafe impl UninitAllocator for BumpArenaLazy { - fn alloc_uninit_slice(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { - self.alloc_uninit_slice(layout) + fn try_alloc_uninit(&self, layout: Layout) -> Option<&mut [MaybeUninit]> { + self.alloc_uninit_impl(layout) } } @@ -290,7 +312,7 @@ unsafe impl UninitAllocator for BumpArenaLazy { #[cfg(feature = "nightly")] unsafe impl core::alloc::Allocator for BumpArenaLazy { fn allocate(&self, layout: Layout) -> Result, core::alloc::AllocError> { - let slice = self.alloc_uninit_slice(layout).ok_or(core::alloc::AllocError)?; + let slice = self.alloc_uninit_impl(layout).ok_or(core::alloc::AllocError)?; // SAFETY: `slice` is guaranteed to be non-null and valid for `layout.size()` bytes. let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) @@ -326,7 +348,7 @@ unsafe impl core::alloc::Allocator for BumpArenaLazy { if required_offset > self.commit.get() { let slice = self - .alloc_uninit_slice_bump(old_offset, required_offset, new_size) + .alloc_uninit_bump(old_offset, required_offset, new_size) .ok_or(core::alloc::AllocError)?; let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; return Ok(NonNull::slice_from_raw_parts(ptr, new_size)); @@ -417,7 +439,7 @@ mod tests { for align in [1, 2, 4, 8, 16] { let layout = Layout::from_size_align(3, align).unwrap(); - let slice = bump.alloc_uninit_slice(layout).unwrap(); + let slice = bump.try_alloc_uninit(layout).unwrap(); let ptr = slice.as_ptr() as usize; assert_eq!(ptr % align, 0); assert_eq!(slice.len(), 3); @@ -434,8 +456,8 @@ mod tests { #[test] fn alloc_no_overlap() { let bump = BumpArenaLazy::new(64); - let a = bump.alloc_uninit_slice(Layout::from_size_align(16, 8).unwrap()).unwrap(); - let b = bump.alloc_uninit_slice(Layout::from_size_align(8, 8).unwrap()).unwrap(); + let a = bump.try_alloc_uninit(Layout::from_size_align(16, 8).unwrap()).unwrap(); + let b = bump.try_alloc_uninit(Layout::from_size_align(8, 8).unwrap()).unwrap(); let a_start = a.as_ptr() as usize; let a_end = a_start + a.len(); @@ -449,11 +471,11 @@ mod tests { fn alloc_oom_does_not_advance() { let bump = BumpArenaLazy::new(16); let layout = Layout::from_size_align(8, 1).unwrap(); - bump.alloc_uninit_slice(layout).unwrap(); + bump.try_alloc_uninit(layout).unwrap(); let used_before = bump.used(); let too_large = Layout::from_size_align(9, 1).unwrap(); - assert!(bump.alloc_uninit_slice(too_large).is_none()); + assert!(bump.try_alloc_uninit(too_large).is_none()); assert_eq!(bump.used(), used_before); assert!(bump.used() <= bump.capacity()); } @@ -462,22 +484,22 @@ mod tests { fn reset_reuses_base() { let bump = BumpArenaLazy::new(32); let layout = Layout::from_size_align(8, 4).unwrap(); - let first = bump.alloc_uninit_slice(layout).unwrap(); + let first = bump.try_alloc_uninit(layout).unwrap(); let first_ptr = first.as_ptr() as usize; unsafe { bump.reset() }; assert_eq!(bump.used(), 0); - let second = bump.alloc_uninit_slice(layout).unwrap(); + let second = bump.try_alloc_uninit(layout).unwrap(); let second_ptr = second.as_ptr() as usize; assert_eq!(first_ptr, second_ptr); } #[test] - fn zero_capacity_rejects_nonzero_alloc_uninit_slice() { + fn zero_capacity_rejects_nonzero_alloc_uninit() { let bump = BumpArenaLazy::new(0); let layout = Layout::from_size_align(1, 1).unwrap(); - assert!(bump.alloc_uninit_slice(layout).is_none()); + assert!(bump.try_alloc_uninit(layout).is_none()); assert_eq!(bump.used(), 0); } @@ -485,7 +507,7 @@ mod tests { fn zero_size_alloc_does_not_advance() { let bump = BumpArenaLazy::new(8); let layout = Layout::from_size_align(0, 8).unwrap(); - let slice = bump.alloc_uninit_slice(layout).unwrap(); + let slice = bump.try_alloc_uninit(layout).unwrap(); assert_eq!(slice.len(), 0); assert_eq!(bump.used(), 0); } diff --git a/src/lib.rs b/src/lib.rs index d5374dc..189c726 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub use typed_arena_ref::*; /// Implementors must uphold the following invariants. Violating any of them /// will cause **undefined behaviour** in safe code that uses [`TypedArenaRef`]. /// -/// - **Alignment** -- every slice returned by [`UninitAllocator::alloc_uninit_slice`] is aligned +/// - **Alignment** -- every slice returned by [`UninitAllocator::try_alloc_uninit`] is aligned /// to at least the requested `layout.align()`. /// - **No overlap** -- the memory regions handed out never overlap. For /// example, a bump allocator achieves this by monotonically advancing a @@ -55,7 +55,7 @@ pub unsafe trait UninitAllocator { /// /// Returns `None` if the allocator cannot satisfy the request. #[allow(clippy::mut_from_ref)] - fn alloc_uninit_slice( + fn try_alloc_uninit( &self, layout: core::alloc::Layout, ) -> Option<&mut [core::mem::MaybeUninit]>; diff --git a/src/typed_arena.rs b/src/typed_arena.rs index c0995df..dbea83e 100644 --- a/src/typed_arena.rs +++ b/src/typed_arena.rs @@ -7,9 +7,9 @@ use core::ptr::{NonNull, drop_in_place}; /// type `T` and automatically drops them in reverse allocation order. /// /// The backing store is a `NonNull<[MaybeUninit]>` whose capacity is -/// set at construction. Each call to [`alloc_raw`] writes a value into -/// the next free slot and returns a mutable reference. When the -/// arena is dropped (or when [`reset`] is called), all live values +/// set at construction. Each call to [`TypedArena::try_alloc`] writes a value into +/// the next free slot and returns a mutable reference. When the +/// arena is dropped (or when [`TypedArena::reset`] is called), all live values /// are dropped and the memory is made available for reuse. /// /// # Invariance and thread safety @@ -29,14 +29,11 @@ use core::ptr::{NonNull, drop_in_place}; /// /// let mut arena = TypedArena::::new(5); /// -/// let s = arena.alloc_raw("hello".to_string()).unwrap(); +/// let s = arena.try_alloc("hello".to_string()).unwrap(); /// assert_eq!(s, "hello"); /// /// // All values are dropped when `arena` goes out of scope. /// ``` -/// -/// [`alloc_raw`]: TypedArena::alloc_raw -/// [`reset`]: TypedArena::reset #[derive(Debug)] pub struct TypedArena { base: NonNull<[MaybeUninit]>, @@ -81,11 +78,32 @@ impl TypedArena { /// use linalloc::TypedArena; /// /// let arena = TypedArena::::new(10); - /// let x = arena.alloc_raw(42).unwrap(); + /// let x = arena.try_alloc(42).unwrap(); /// assert_eq!(*x, 42); /// ``` - #[allow(clippy::mut_from_ref)] + #[deprecated(since = "1.2.0", note = "Use `TypedArena::try_alloc` instead.")] pub fn alloc_raw(&self, value: T) -> Option<&mut T> { + self.alloc_impl(value) + } + + /// Just like [`TypedArena::try_alloc`], but panics + /// when the arena capacity is full. + /// + /// # Panics + /// + /// If `TypedArena::len() == TypedArena::capacity()`. + pub fn alloc(&self, value: T) -> &mut T { + self.alloc_impl(value).expect("TypedArena capacity is full") + } + + /// A carbon copy of [`TypedArena::alloc_raw`], but with a + /// more bespoke name and will likely be the stable API going forward. + 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::() == 0 { unsafe { let dangling = NonNull::::dangling(); @@ -121,7 +139,7 @@ impl TypedArena { /// /// let mut arena = TypedArena::::new(10); /// assert_eq!(arena.len(), 0); - /// arena.alloc_raw(1); + /// arena.try_alloc(1); /// assert_eq!(arena.len(), 1); /// ``` pub fn len(&self) -> usize { @@ -137,7 +155,7 @@ impl TypedArena { /// /// let arena = TypedArena::::new(10); /// assert!(arena.is_empty()); - /// arena.alloc_raw(1); + /// arena.try_alloc(1); /// assert!(!arena.is_empty()); /// ``` pub fn is_empty(&self) -> bool { @@ -154,7 +172,7 @@ impl TypedArena { /// /// Because this method takes `&mut self`, the borrow checker /// guarantees that no references to the arena's contents are - /// currently alive. After the call, `len()` returns `0`. + /// currently alive. After the call, `len()` returns `0`. /// /// # Examples /// @@ -163,7 +181,7 @@ impl TypedArena { /// /// let mut arena = TypedArena::>::new(5); /// { - /// let v = arena.alloc_raw(vec![1, 2, 3]).unwrap(); + /// let v = arena.try_alloc(vec![1, 2, 3]).unwrap(); /// } // v goes out of scope -- can call `reset` now. /// arena.reset(); /// assert_eq!(arena.len(), 0); @@ -218,9 +236,9 @@ mod tests { let order = Cell::new(Vec::new()); let arena = TypedArena::::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(); + arena.try_alloc(DropTracker { id: 1, order: &order }).unwrap(); + arena.try_alloc(DropTracker { id: 2, order: &order }).unwrap(); + arena.try_alloc(DropTracker { id: 3, order: &order }).unwrap(); drop(arena); @@ -232,8 +250,8 @@ mod tests { let order = Cell::new(Vec::new()); let mut arena = TypedArena::::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()); + let ptr1 = ptr::from_mut(arena.try_alloc(DropTracker { id: 1, order: &order }).unwrap()); + let _ptr2 = ptr::from_mut(arena.try_alloc(DropTracker { id: 2, order: &order }).unwrap()); arena.reset(); @@ -241,7 +259,7 @@ mod tests { assert_eq!(order.take(), vec![2, 1]); // New allocation reuses the first slot. - let ptr3 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap()); + let ptr3 = ptr::from_mut(arena.try_alloc(DropTracker { id: 3, order: &order }).unwrap()); assert_eq!(ptr1, ptr3); drop(arena); @@ -260,13 +278,13 @@ mod tests { let count = Cell::new(0u32); let mut arena = TypedArena::::new(10); - arena.alloc_raw(Counter(&count)).unwrap(); - arena.alloc_raw(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); arena.reset(); assert_eq!(count.get(), 2); // both dropped exactly once - arena.alloc_raw(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); drop(arena); assert_eq!(count.get(), 3); // only the new one dropped } @@ -283,8 +301,8 @@ mod tests { let drops = Cell::new(0u32); let mut arena = core::mem::ManuallyDrop::new(TypedArena::::new(2)); - arena.alloc_raw(PanicOnDrop(&drops)).unwrap(); - arena.alloc_raw(PanicOnDrop(&drops)).unwrap(); + arena.try_alloc(PanicOnDrop(&drops)).unwrap(); + arena.try_alloc(PanicOnDrop(&drops)).unwrap(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| arena.reset())); @@ -299,25 +317,25 @@ mod tests { #[test] fn oom_does_not_advance_offset() { let arena = TypedArena::::new(1); // holds exactly 1 u64 - assert!(arena.alloc_raw(1u64).is_some()); + assert!(arena.try_alloc(1u64).is_some()); assert_eq!(arena.len(), 1); - assert!(arena.alloc_raw(2u64).is_none()); + assert!(arena.try_alloc(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!(arena.try_alloc(()).is_some()); assert_eq!(arena.len(), 0); - assert!(arena.alloc_raw(()).is_some()); + assert!(arena.try_alloc(()).is_some()); assert_eq!(arena.len(), 0); } #[test] fn allocated_value_is_valid() { let arena = TypedArena::::new(1); - let s = arena.alloc_raw("hello".to_string()).unwrap(); + let s = arena.try_alloc("hello".to_string()).unwrap(); assert_eq!(s, "hello"); s.push_str(" world"); assert_eq!(s, "hello world"); diff --git a/src/typed_arena_lazy.rs b/src/typed_arena_lazy.rs index 37cca97..253bbeb 100644 --- a/src/typed_arena_lazy.rs +++ b/src/typed_arena_lazy.rs @@ -30,7 +30,7 @@ use crate::sys; /// /// let mut arena = TypedArenaLazy::::new(5); /// -/// let s = arena.alloc_raw("hello".to_string()).unwrap(); +/// let s = arena.try_alloc("hello".to_string()).unwrap(); /// assert_eq!(s, "hello"); /// /// // All values are dropped when `arena` goes out of scope. @@ -101,6 +101,16 @@ impl TypedArenaLazy { }) } + /// Just like [`TypedArenaLazy::try_alloc`], but panics if the allocation + /// fails. + /// + /// # Panics + /// + /// Panics if the arena is full or a required memory commit fails. + pub fn alloc(&self, value: T) -> &mut T { + self.alloc_impl(value).expect("TypedArenaLazy allocation failed") + } + /// Allocates a new `T` by moving `value` into the arena. /// /// The returned mutable reference borrows the arena immutably (`&self`), @@ -120,11 +130,21 @@ impl TypedArenaLazy { /// use linalloc::TypedArenaLazy; /// /// let arena = TypedArenaLazy::::new(10); - /// let x = arena.alloc_raw(42).unwrap(); + /// let x = arena.try_alloc(42).unwrap(); /// assert_eq!(*x, 42); /// ``` - #[allow(clippy::mut_from_ref)] + pub fn try_alloc(&self, value: T) -> Option<&mut T> { + self.alloc_impl(value) + } + + /// Allocates a new `T` by moving `value` into the arena. + #[deprecated(since = "1.2.0", note = "Use `TypedArenaLazy::try_alloc` instead.")] pub fn alloc_raw(&self, value: T) -> Option<&mut T> { + self.alloc_impl(value) + } + + #[allow(clippy::mut_from_ref)] + fn alloc_impl(&self, value: T) -> Option<&mut T> { // ZST's never consume capacity. if size_of::() == 0 { unsafe { @@ -144,7 +164,7 @@ impl TypedArenaLazy { // Ensure enough memory is committed. if required_bytes > self.commit.get() { - return self.alloc_raw_bump(idx, required_bytes, value); + return self.alloc_bump(idx, required_bytes, value); } // Initialise the slot. @@ -156,11 +176,11 @@ impl TypedArenaLazy { } } - // With the code in `alloc_raw_bump()` out of the way, `alloc_raw()` compiles down to some super tight assembly. + // With the code in `alloc_bump()` out of the way, `alloc_impl()` compiles down to some super tight assembly. #[cold] #[inline(never)] #[allow(clippy::mut_from_ref)] - fn alloc_raw_bump(&self, idx: usize, required_bytes: usize, value: T) -> Option<&mut T> { + fn alloc_bump(&self, idx: usize, required_bytes: usize, value: T) -> Option<&mut T> { let page = sys::page_size(); let current = self.commit.get(); @@ -224,7 +244,7 @@ impl TypedArenaLazy { /// /// let mut arena = TypedArenaLazy::::new(10); /// assert_eq!(arena.len(), 0); - /// arena.alloc_raw(1); + /// arena.try_alloc(1); /// assert_eq!(arena.len(), 1); /// ``` pub fn len(&self) -> usize { @@ -240,7 +260,7 @@ impl TypedArenaLazy { /// /// let arena = TypedArenaLazy::::new(10); /// assert!(arena.is_empty()); - /// arena.alloc_raw(1); + /// arena.try_alloc(1); /// assert!(!arena.is_empty()); /// ``` pub fn is_empty(&self) -> bool { @@ -269,7 +289,7 @@ impl TypedArenaLazy { /// /// let mut arena = TypedArenaLazy::>::new(5); /// { - /// let v = arena.alloc_raw(vec![1, 2, 3]).unwrap(); + /// let v = arena.try_alloc(vec![1, 2, 3]).unwrap(); /// } // v goes out of scope -- can call `reset` now. /// arena.reset(); /// assert_eq!(arena.len(), 0); @@ -327,9 +347,9 @@ mod tests { let order = Cell::new(Vec::new()); let arena = TypedArenaLazy::::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(); + arena.try_alloc(DropTracker { id: 1, order: &order }).unwrap(); + arena.try_alloc(DropTracker { id: 2, order: &order }).unwrap(); + arena.try_alloc(DropTracker { id: 3, order: &order }).unwrap(); drop(arena); @@ -341,8 +361,8 @@ mod tests { let order = Cell::new(Vec::new()); let mut arena = TypedArenaLazy::::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()); + let ptr1 = ptr::from_mut(arena.try_alloc(DropTracker { id: 1, order: &order }).unwrap()); + let _ptr2 = ptr::from_mut(arena.try_alloc(DropTracker { id: 2, order: &order }).unwrap()); arena.reset(); @@ -350,7 +370,7 @@ mod tests { assert_eq!(order.take(), vec![2, 1]); // New allocation reuses the first slot. - let ptr3 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap()); + let ptr3 = ptr::from_mut(arena.try_alloc(DropTracker { id: 3, order: &order }).unwrap()); assert_eq!(ptr1, ptr3); drop(arena); @@ -369,13 +389,13 @@ mod tests { let count = Cell::new(0u32); let mut arena = TypedArenaLazy::::new(10); - arena.alloc_raw(Counter(&count)).unwrap(); - arena.alloc_raw(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); arena.reset(); assert_eq!(count.get(), 2); // both dropped exactly once - arena.alloc_raw(Counter(&count)).unwrap(); + arena.try_alloc(Counter(&count)).unwrap(); drop(arena); assert_eq!(count.get(), 3); // only the new one dropped } @@ -392,8 +412,8 @@ mod tests { let drops = Cell::new(0u32); let mut arena = mem::ManuallyDrop::new(TypedArenaLazy::::new(2)); - arena.alloc_raw(PanicOnDrop(&drops)).unwrap(); - arena.alloc_raw(PanicOnDrop(&drops)).unwrap(); + arena.try_alloc(PanicOnDrop(&drops)).unwrap(); + arena.try_alloc(PanicOnDrop(&drops)).unwrap(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| arena.reset())); @@ -408,25 +428,25 @@ mod tests { #[test] fn oom_does_not_advance_offset() { let arena = TypedArenaLazy::::new(1); // holds exactly 1 u64 - assert!(arena.alloc_raw(1u64).is_some()); + assert!(arena.try_alloc(1u64).is_some()); assert_eq!(arena.len(), 1); - assert!(arena.alloc_raw(2u64).is_none()); + assert!(arena.try_alloc(2u64).is_none()); assert_eq!(arena.len(), 1); } #[test] fn zst_does_not_advance_offset() { let arena = TypedArenaLazy::<()>::new(0); - assert!(arena.alloc_raw(()).is_some()); + assert!(arena.try_alloc(()).is_some()); assert_eq!(arena.len(), 0); - assert!(arena.alloc_raw(()).is_some()); + assert!(arena.try_alloc(()).is_some()); assert_eq!(arena.len(), 0); } #[test] fn allocated_value_is_valid() { let arena = TypedArenaLazy::::new(1); - let s = arena.alloc_raw("hello".to_string()).unwrap(); + let s = arena.try_alloc("hello".to_string()).unwrap(); assert_eq!(s, "hello"); s.push_str(" world"); assert_eq!(s, "hello world"); diff --git a/src/typed_arena_ref.rs b/src/typed_arena_ref.rs index bebddc3..654cf11 100644 --- a/src/typed_arena_ref.rs +++ b/src/typed_arena_ref.rs @@ -129,7 +129,7 @@ impl<'a, T, A: UninitAllocator + 'a> TypedArenaRef<'a, T, A> { } let layout = Layout::new::(); - let slice = self.allocator.alloc_uninit_slice(layout)?; + let slice = self.allocator.try_alloc_uninit(layout)?; let ptr = slice.as_mut_ptr().cast::(); unsafe {