Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "linalloc"
version = "1.1.0"
version = "1.2.0"
edition = "2024"
rust-version = "1.95"
description = """
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -41,7 +49,7 @@ use core::alloc::Layout;
use linalloc::BumpArena;

let arena = BumpArena::new(128);
let slot = arena.alloc_uninit_slice(Layout::new::<u64>()).unwrap();
let slot = arena.try_alloc_uninit(Layout::new::<u64>()).unwrap();
let ptr = slot.as_mut_ptr().cast::<u64>();

unsafe { ptr.write(42) };
Expand Down Expand Up @@ -87,7 +95,7 @@ the arena is reset or dropped.
use linalloc::TypedArena;

let arena = TypedArena::<String>::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");
Expand All @@ -100,7 +108,7 @@ are still live:
use linalloc::TypedArena;

let mut arena = TypedArena::<String>::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
Expand Down Expand Up @@ -157,8 +165,8 @@ know whether the OS refused more committed memory.
}

let typed = TypedArenaLazy::<u8>::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);
}
```
Expand Down
61 changes: 41 additions & 20 deletions src/bump_arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::UninitAllocator;
///
/// // Allocate space for a `u64`.
/// let layout = Layout::new::<u64>();
/// let slice = bump.alloc_uninit_slice(layout).unwrap();
/// let slice = bump.try_alloc_uninit(layout).unwrap();
/// let ptr = slice.as_mut_ptr().cast::<u64>();
/// unsafe { ptr.write(42) };
/// let val = unsafe { &*ptr };
Expand Down Expand Up @@ -68,29 +68,50 @@ impl BumpArena {
}
}

/// Allocates a mutable slice of [`MaybeUninit<u8>`] 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<u8>] {
self.alloc_uninit_impl(layout).expect("BumpArena allocation failed")
}

/// Allocates a mutable slice of [`MaybeUninit<u8>`] 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.
///
/// # Returns
///
/// `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<u8>]> {
self.alloc_uninit_impl(layout)
}

/// Allocates a mutable slice of [`MaybeUninit<u8>`] 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<u8>]> {
self.alloc_uninit_impl(layout)
}

#[allow(clippy::mut_from_ref)]
fn alloc_uninit_impl(&self, layout: Layout) -> Option<&mut [MaybeUninit<u8>]> {
let size = layout.size();
if size == 0 {
let ptr = layout.dangling_ptr().as_ptr().cast::<MaybeUninit<u8>>();
Expand Down Expand Up @@ -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<u8>]> {
self.alloc_uninit_slice(layout)
fn try_alloc_uninit(&self, layout: Layout) -> Option<&mut [MaybeUninit<u8>]> {
self.alloc_uninit_impl(layout)
}
}

Expand All @@ -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<NonNull<[u8]>, 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()))
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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());
}
Expand All @@ -343,30 +364,30 @@ 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);
}

#[test]
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);
}
Expand Down
Loading