# fastarena — Complete Usage Guide
> Zero-dependency bump-pointer arena allocator with RAII transactions, nested savepoints, optional destructor tracking, and `ArenaVec` — built for compilers, storage engines, and high-throughput request-scoped workloads.
---
## Table of Contents
- [Why fastarena?](#why-fastarena)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Basic Allocation](#basic-allocation)
- [Transactions — Auto-Rollback on Failure](#transactions--auto-rollback-on-failure)
- [Nested Savepoints](#nested-savepoints)
- [ArenaVec with `finish()`](#arenavec-with-finish--transfer-ownership-to-the-arena)
- [ArenaVec Macro (`arenavec!`)](#arenavec-macro)
- [Transaction Budgets](#transaction-budgets--cap-memory-per-request)
- [Drop-Tracking](#drop-tracking--opt-in-destructor-execution)
- [API Reference](#api-reference)
- [Arena](#arena)
- [ArenaBox](#arenabox)
- [Transaction](#transaction)
- [ArenaVec](#arenavec)
- [ArenaStats](#arenastats)
- [Checkpoint](#checkpoint)
- [TxnStatus](#txnstatus)
- [TxnDiff](#txndiff)
- [TryReserveError](#tryreserveerror)
- [ArenaVecIntoIter](#arenavecintoiter)
- [Performance](#performance)
- [Best Practices](#best-practices)
- [Feature Flags](#feature-flags)
- [Safety Considerations](#safety-considerations)
- [MSRV](#minimum-supported-rust-version)
---
## Why fastarena?
| O(1) bump allocation | Yes | Yes | Yes |
| Heterogeneous types | Yes | Yes | No |
| Checkpoint / Rewind | Yes (O(1) snapshot) | No | No |
| RAII Transaction | Yes | No | No |
| Nested Savepoints | Yes | No | No |
| Transaction Budget | Yes | No | No |
| Transaction Metrics | Yes (`TxnDiff`) | No | No |
| `with_transaction` closure API | Yes | No | No |
| `ArenaVec::finish()` ownership transfer | Yes | No | N/A |
| Drop-tracking (opt-in) | Yes | No | Forced |
| Fallible allocation | Yes | Partial | No |
| `alloc_cache_aligned` | Yes | No | No |
| O(1) stats with utilization | Yes | Partial | No |
| `arenavec!` macro | Yes | No | No |
| Zero dependencies | Yes | Yes | Yes |
---
## Installation
```toml
[dependencies]
fastarena = "0.2"
```
With destructor tracking:
```toml
[dependencies]
fastarena = { version = "0.2", features = ["drop-tracking"] }
```
---
## Quick Start
### Basic Allocation
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let x: &mut u64 = arena.alloc(42);
let squares: &mut [u32] = arena.alloc_slice(0u32..8);
let s: &str = arena.alloc_str("hello");
// Zero-cost reset — pages stay warm, no OS calls
arena.reset();
```
### Transactions — Auto-Rollback on Failure
No other arena allocator gives you RAII transactions. Allocations succeed or roll back as a unit — no leaks, no manual cleanup.
> **Note:** Each `alloc` call returns a `&mut T` that borrows the transaction.
> Don't hold references across subsequent allocations — use the value and let it
> drop before calling `alloc` again.
```rust
use fastarena::Arena;
let mut arena = Arena::new();
// Ok commits, Err rolls back — all allocations are atomic
let result: Result<u32, &str> = arena.with_transaction(|txn| {
txn.alloc_str("fastarena");
let score = txn.alloc(100u32);
Ok(*score)
});
assert_eq!(result, Ok(100));
// Failed transaction — everything allocated inside is gone
arena.with_transaction(|txn| {
txn.alloc(1u32);
txn.alloc(2u32);
Err("abort") // both u32s rolled back automatically
});
// Infallible variant — commits even through panic
});
assert_eq!(val, 42);
```
### Nested Savepoints
Transactions nest to arbitrary depth. Each savepoint is independently committable — roll back an inner scope without losing outer work.
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let mut outer = arena.transaction();
outer.alloc_str("top-level");
{
let mut inner = outer.savepoint();
inner.alloc_str("speculative-opt");
inner.alloc(999u32);
// dropped without commit — inner work discarded, outer untouched
}
outer.alloc_str("confirmed");
outer.commit(); // "top-level" + "confirmed" survive
```
### Multiple Allocations and the Borrow Checker
All `alloc*` methods return `&mut T`. This prevents making multiple allocations simultaneously because the borrow checker sees the arena as mutably borrowed:
```ignore
// This does NOT compile:
let mut arena = Arena::new();
let x = arena.alloc(1i32); // &mut i32
let y = arena.alloc(2i32); // ERROR: cannot borrow arena as mutable more than once
```
**Workarounds:**
1. **Immediate consumption** — transform the value before allocating another:
```rust
let mut arena = Arena::new();
let x = arena.alloc(1i32);
let sum = *x + 10; let y = arena.alloc(sum);
```
2. **Store as raw pointer** — convert the reference to a raw pointer after allocation:
```rust
let mut arena = Arena::new();
let x: *mut i32 = arena.alloc(1i32) as *mut _;
let y = arena.alloc(2i32);
```
3. **Use `ArenaVec`** — for multiple items of the same type:
```rust
let mut arena = Arena::new();
let mut vec = ArenaVec::new(&mut arena);
vec.push(1);
vec.push(2);
let slice = vec.finish(); ```
4. **Use `ArenaBox<T>`** — for owned allocation with drop semantics:
```rust
let mut arena = Arena::new();
let x = arena.alloc_box(1i32);
assert_eq!(*x, 1);
```
### ArenaVec with `finish()` — Transfer Ownership to the Arena
`ArenaVec` is a growable vector backed by arena memory. Call `finish()` to hand ownership to the arena — the slice lives as long as the arena, no copy. With `drop-tracking` enabled, elements are registered for destruction on the next `reset` / `rewind`; without the feature, no destructors run.
```rust
use fastarena::{Arena, ArenaVec};
let mut arena = Arena::new();
let items: &mut [u32] = {
let mut v = ArenaVec::new(&mut arena);
for i in 0..1024 {
v.push(i);
}
v.finish() // ArenaVec consumed, slice now arena-owned
};
assert_eq!(items.len(), 1024);
assert_eq!(items[512], 512);
```
Or use the `arenavec!` macro for concise initialization:
```rust
use fastarena::{Arena, arenavec};
let mut arena = Arena::new();
let items: &mut [u32] = arenavec![in &mut arena; 0u32; 1024].finish();
assert_eq!(items[512], 0);
let values: &mut [u32] = arenavec![in &mut arena; 1, 2, 3].finish();
assert_eq!(values, &mut [1, 2, 3]);
```
### Transaction Budgets — Cap Memory per Request
Set a byte budget on any transaction. Exceed it and `alloc` panics (or `try_alloc` returns `None`). Zero-cost when unlimited.
> **Note:** `alloc(vec![0u8; N])` stores only the `Vec` struct (24 bytes) in the
> arena — the heap buffer is *not* arena-tracked. To budget actual data bytes,
> use `alloc_slice` or `alloc_slice_copy` instead.
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let mut txn = arena.transaction();
txn.set_limit(4096); // hard cap
// GOOD — alloc_slice copies data into arena, budget sees all bytes
txn.alloc_slice(vec![0u8; 2048]); // ok — 2048 arena bytes
// txn.alloc_slice(vec![0u8; 4096]); // panics: budget exceeded (2048 + 4096 > 4096)
// BAD — alloc(vec![...]) stores only the 24-byte Vec struct, heap is untracked
// txn.alloc(vec![0u8; 9999]); // would NOT panic — budget sees 24 bytes
let remaining = txn.budget_remaining(); // introspect at any time
txn.commit();
```
### Drop-Tracking — Opt-In Destructor Execution
By default, fastarena never runs destructors (zero overhead). Enable `drop-tracking` to run them in LIFO order on `reset()` / `rewind()`.
```toml
[dependencies]
fastarena = { version = "0.2", features = ["drop-tracking"] }
```
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let cp = arena.checkpoint();
arena.alloc(String::from("hello"));
arena.alloc(String::from("world"));
// With drop-tracking: drops fire in LIFO order ("world", then "hello")
// Without drop-tracking: no destructors, memory reclaimed instantly
arena.rewind(cp);
```
---
## API Reference
### Arena
The main bump-pointer allocator.
#### Constructors
| `Arena::new()` | Creates arena with 64 KiB initial block |
| `Arena::with_capacity(bytes)` | Creates arena with custom initial block size |
```rust
let arena = Arena::new();
let arena = Arena::with_capacity(1024 * 1024); // 1 MiB initial
```
#### Allocation Methods
| `alloc` | `fn alloc<T>(&mut self, val: T) -> &mut T` | O(1) single value |
| `alloc_with` | `fn alloc_with<T, F>(&mut self, f: F) -> &mut T` | Construct via closure |
| `alloc_box` | `fn alloc_box<T>(&mut self, val: T) -> ArenaBox<'_, T>` | Owned allocation (`Box`-like) |
| `alloc_slice` | `fn alloc_slice<T, I>(&mut self, iter: I) -> &mut [T]` | From `ExactSizeIterator` |
| `alloc_slice_copy` | `fn alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> &mut [T]` | Single `memcpy` for `Copy` types |
| `alloc_slice_fill` | `fn alloc_slice_fill<T: Clone>(&mut self, n: usize, val: T) -> &mut [T]` | `n` clones of `val` |
| `alloc_slice_default` | `fn alloc_slice_default<T: Default>(&mut self, n: usize) -> &mut [T]` | `n` defaults |
| `alloc_slice_uninit` | `fn alloc_slice_uninit<T>(&mut self, n: usize) -> &mut [MaybeUninit<T>]` | Uninit slice |
| `alloc_str` | `fn alloc_str(&mut self, s: &str) -> &str` | Copy string into arena |
| `alloc_uninit` | `fn alloc_uninit<T>(&mut self) -> &mut MaybeUninit<T>` | Uninit single slot |
| `alloc_raw` | `fn alloc_raw(&mut self, size: usize, align: usize) -> NonNull<u8>` | Raw bytes with alignment |
| `alloc_zeroed` | `fn alloc_zeroed(&mut self, size: usize, align: usize) -> NonNull<u8>` | Zeroed raw bytes |
| `alloc_cache_aligned` | `fn alloc_cache_aligned(&mut self, size: usize) -> NonNull<u8>` | 64-byte aligned (SIMD-friendly) |
```rust
let mut arena = Arena::new();
let p = arena.alloc(Point { x: 1.0, y: 2.0 });
let slice = arena.alloc_slice(0u32..100);
let zeros: &mut [u64] = arena.alloc_slice_default(16);
let sevens: &mut [u32] = arena.alloc_slice_fill(8, 7);
let s = arena.alloc_str("hello");
let raw = arena.alloc_raw(4096, 4096); // page-aligned
let buf = arena.alloc_cache_aligned(256); // cache-line aligned
```
#### Fallible Allocation
All allocation methods have `try_*` variants returning `Option`:
| `try_alloc` | `fn try_alloc<T>(&mut self, val: T) -> Option<&mut T>` |
| `try_alloc_slice` | `fn try_alloc_slice<T, I>(&mut self, iter: I) -> Option<&mut [T]>` |
| `try_alloc_slice_copy` | `fn try_alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> Option<&mut [T]>` |
| `try_alloc_slice_uninit` | `fn try_alloc_slice_uninit<T>(&mut self, n: usize) -> Option<&mut [MaybeUninit<T>]>` |
| `try_alloc_str` | `fn try_alloc_str(&mut self, s: &str) -> Option<&str>` |
| `try_alloc_raw` | `fn try_alloc_raw(&mut self, size: usize, align: usize) -> Option<NonNull<u8>>` |
| `try_alloc_zeroed` | `fn try_alloc_zeroed(&mut self, size: usize, align: usize) -> Option<NonNull<u8>>` |
| `try_alloc_cache_aligned` | `fn try_alloc_cache_aligned(&mut self, size: usize) -> Option<NonNull<u8>>` |
```rust
let mut arena = Arena::with_capacity(64);
match arena.try_alloc(42u64) {
Some(val) => { /* success */ }
None => { /* out of memory */ }
}
```
#### Checkpoint / Rewind / Reset
| `checkpoint` | `fn checkpoint(&self) -> Checkpoint` | O(1) | Snapshot current position |
| `rewind` | `fn rewind(&mut self, cp: Checkpoint)` | O(k) | Roll back to checkpoint |
| `reset` | `fn reset(&mut self)` | O(p) | Reset entire arena |
> **`reset()` complexity:** O(p) where p = peak block count since last reset.
> A `high_water_mark` tracks the highest block index ever reached. On reset,
> only blocks 0..=high_water_mark have their offsets zeroed — all their
> capacity is fully reusable with no waste. Single-block arenas pay O(1).
> No memory is freed — OS pages stay mapped and TLB-warm.
```rust
let mut arena = Arena::new();
arena.alloc(1u64);
let cp = arena.checkpoint(); // O(1) — copies 3 integers
arena.alloc(2u64);
arena.alloc(3u64);
arena.rewind(cp); // 2 and 3 gone; blocks retained for reuse
// reset() — reclaims all memory for reuse, O(peak_blocks)
arena.reset(); // fast, warm pages, no OS calls
```
#### Transaction Methods
| `transaction` | `fn transaction(&mut self) -> Transaction<'_>` | Open manual transaction |
| `with_transaction` | `fn with_transaction<F, T, E>(&mut self, f: F) -> Result<T, E>` | Ok=commit, Err=rollback |
| `with_transaction_infallible` | `fn with_transaction_infallible<F, T>(&mut self, f: F) -> T` | Commits even through panic |
| `transaction_depth` | `fn transaction_depth(&self) -> usize` | Current nesting depth |
#### Introspection
| `stats` | `fn stats(&self) -> ArenaStats` | O(1) |
| `block_count` | `fn block_count(&self) -> usize` | O(1) |
| `remaining` | `fn remaining(&self) -> usize` | O(1) |
| `contains_ptr` | `fn contains_ptr(&self, ptr: *const u8) -> bool` | O(blocks) |
`remaining()` returns the bytes left in the current block — useful for
pre-flight checks before a large allocation. `contains_ptr` checks whether
a raw pointer lies inside any block this arena owns; useful for sanitiser
integration and debug assertions.
#### Drop Registry (drop-tracking feature only)
| `register_drop` | `unsafe fn register_drop<T>(&mut self, ptr: *mut T)` | Register a single value's destructor |
| `register_drop_slice` | `unsafe fn register_drop_slice<T>(&mut self, ptr: *mut T, count: usize)` | Register a contiguous run of destructors (O(1)) |
These are no-ops when the `drop-tracking` feature is disabled.
---
### ArenaBox
An owned, single-element allocation from an arena, analogous to
[`Box<T>`](https://doc.rust-lang.org/std/boxed/struct.Box.html) but backed by
arena memory.
#### Construction
```rust
use fastarena::{Arena, ArenaBox};
use std::mem::MaybeUninit;
let mut arena = Arena::new();
// Direct construction:
let b = arena.alloc_box(42i32);
drop(b);
// Via closure (avoids stack copy for large T):
let big: ArenaBox<[u8; 1024]> = ArenaBox::new_with(&mut arena, || [0u8; 1024]);
drop(big);
// With deferred initialisation:
let mut slot: ArenaBox<MaybeUninit<u64>> = ArenaBox::new_uninit(&mut arena);
slot.write(7);
let init: ArenaBox<u64> = unsafe { ArenaBox::assume_init(slot) };
assert_eq!(*init, 7);
```
#### Methods
| `into_inner` | `fn into_inner(self) -> T` | Consume and extract the value |
| `as_ptr` | `fn as_ptr(&self) -> *const T` | Raw pointer to the data |
| `as_mut_ptr` | `fn as_mut_ptr(&mut self) -> *mut T` | Mutable raw pointer |
| `into_raw` | `fn into_raw(b: Self) -> NonNull<T>` | Leak the box, return raw pointer |
| `from_raw` | `unsafe fn from_raw(arena, ptr) -> Self` | Reconstruct from `into_raw` |
| `leak` | `fn leak(b: Self) -> &'arena mut T` | Leak the box, return `&'arena mut T` |
| `new_with` | `fn new_with(arena, f: FnOnce() -> T) -> Self` | Construct via closure |
| `new_uninit` | `fn new_uninit(arena) -> ArenaBox<MaybeUninit<T>>` | Allocate uninitialised slot |
| `assume_init` | `unsafe fn assume_init(b: ArenaBox<MaybeUninit<T>>) -> ArenaBox<T>` | Promote initialised slot |
#### Trait Implementations
`ArenaBox<T>` implements `Deref<Target = T>`, `DerefMut`, `AsRef<T>`,
`AsMut<T>`, `Borrow<T>`, `BorrowMut<T>`, `Debug`, `Display`, `Pointer`,
and forwards `PartialEq` / `Eq` / `PartialOrd` / `Ord` / `Hash` to `T`.
`Send` and `Sync` are implemented when `T: Send` / `T: Sync` (matching
`Box<T>`).
```rust
let mut arena = Arena::new();
let mut b = arena.alloc_box(42i32);
// Deref / DerefMut work transparently
assert_eq!(*b, 42);
*b = 100;
assert_eq!(*b, 100);
// PartialEq forwards to the inner value
let a = arena.alloc_box(1u32);
// (assertions omitted — borrow checker disallows two boxes side-by-side)
```
#### Drop Semantics
`ArenaBox<T>` runs `T`'s destructor when dropped — the same behaviour as
`std::boxed::Box<T>`. The arena memory itself is reclaimed in bulk by
[`Arena::reset`] / [`Arena::rewind`] / arena drop. The `drop-tracking`
feature is **not required** for `ArenaBox`; the box owns its own
destruction.
```rust
use fastarena::{Arena, ArenaBox};
let mut arena = Arena::new();
{
let _b: ArenaBox<String> = arena.alloc_box(String::from("hello"));
// _b dropped here — String::drop runs, freeing the heap buffer.
}
// arena memory still holds the (now-dropped) bytes; reclaimed on reset.
```
The lifetime `'arena` ties the `ArenaBox` to the arena it was allocated
from. The borrow checker prevents `Arena::reset` / `Arena::rewind` while
any `ArenaBox` from this arena is alive.
---
### Transaction
A scoped RAII guard over an `Arena`. Auto-rolls back on drop unless committed.
#### Lifecycle
| `commit` | `fn commit(self) -> TxnStatus` | Keep all allocations |
| `rollback` | `fn rollback(self) -> TxnStatus` | Explicit rollback |
| `savepoint` | `fn savepoint(&mut self) -> Transaction<'_>` | Open nested transaction |
#### Budget
| `set_limit` | `fn set_limit(&mut self, bytes: usize)` | Set byte cap |
| `budget_remaining` | `fn budget_remaining(&self) -> Option<usize>` | Remaining budget |
#### Introspection
| `bytes_used` | `fn bytes_used(&self) -> usize` | Bytes allocated since open |
| `diff` | `fn diff(&self) -> TxnDiff` | Allocation metrics |
| `depth` | `fn depth(&self) -> usize` | Nesting depth (1=top-level) |
| `arena_depth` | `fn arena_depth(&self) -> usize` | Arena's total nesting depth |
| `checkpoint` | `fn checkpoint(&self) -> Checkpoint` | The saved checkpoint |
| `arena_mut` | `fn arena_mut(&mut self) -> &mut Arena` | Direct arena access |
| `is_committed` | `fn is_committed(&self) -> bool` | **Deprecated** — always returns `false` (commit consumes `self`) |
#### Allocation
`Transaction` exposes the same allocation surface as `Arena` —
`alloc`, `alloc_with`, `alloc_slice`, `alloc_slice_copy`,
`alloc_slice_fill`, `alloc_slice_default`, `alloc_slice_uninit`,
`alloc_str`, `alloc_uninit`, `alloc_raw`, `alloc_zeroed`,
`alloc_cache_aligned`, plus all `try_*` variants — every method
budget-checked against [`set_limit`](#budget) when set.
#### Usage Patterns
**Closure API (recommended):**
```rust
let result = arena.with_transaction(|txn| {
let x = txn.alloc(21u32);
Ok(*x * 2)
}); // Ok(42) — committed
let result = arena.with_transaction(|txn| {
txn.alloc(1u32);
Err("fail")
}); // Err("fail") — rolled back
```
**Manual API:**
```rust
let mut txn = arena.transaction();
txn.alloc(1u32);
txn.alloc(2u32);
txn.commit(); // or drop to rollback
```
**Nested savepoints:**
```rust
let mut outer = arena.transaction();
outer.alloc(1u32);
{
let mut inner = outer.savepoint();
inner.alloc(2u32);
// dropped — only inner rolled back
}
outer.commit(); // 1 survives
```
**Budget enforcement:**
```rust
let mut txn = arena.transaction();
txn.set_limit(1024);
txn.alloc_slice(vec![0u8; 512]); // ok — 512 arena bytes
// txn.alloc_slice(vec![0u8; 1024]); // panics: budget exceeded
txn.try_alloc_slice(vec![0u8; 1024]); // returns None
txn.commit();
```
---
### ArenaVec
An append-only growable vector backed by arena memory.
#### Construction
| `new` | `fn new(arena: &'arena mut Arena) -> Self` | Empty vector |
| `with_capacity` | `fn with_capacity(arena: &'arena mut Arena, cap: usize) -> Self` | Pre-allocated |
#### Operations
| `push` | `fn push(&mut self, val: T)` | Amortized O(1) append |
| `try_push` | `fn try_push(&mut self, val: T) -> Result<(), T>` | Fallible push; returns value on OOM |
| `pop` | `fn pop(&mut self) -> Option<T>` | Remove last element |
| `swap_remove` | `fn swap_remove(&mut self, i: usize) -> T` | O(1) removal (does not preserve order) |
| `remove_item` | `fn remove_item(&mut self, item: &T) -> bool where T: PartialEq` | Find + `swap_remove` |
| `clear` | `fn clear(&mut self)` | Clear, keep capacity |
| `retain` | `fn retain<F>(&mut self, f: F)` | Keep elements where `f(&T) -> true` |
| `retain_mut` | `fn retain_mut<F>(&mut self, f: F)` | Like `retain` with `&mut T` |
| `dedup` | `fn dedup(&mut self) where T: PartialEq` | Drop consecutive duplicates |
| `dedup_by` | `fn dedup_by<F>(&mut self, eq: F)` | Custom comparator |
| `extend_exact` | `fn extend_exact<I>(&mut self, iter: I)` | Batch from `ExactSizeIterator` |
| `extend_from_slice` | `fn extend_from_slice(&mut self, slice: &[T]) where T: Copy` | Single `memcpy` |
| `truncate` | `fn truncate(&mut self, len: usize)` | Shorten, dropping excess |
| `resize` | `fn resize(&mut self, new_len: usize, val: T) where T: Clone` | Grow or shrink |
| `finish` | `fn finish(self) -> &'arena mut [T]` | Transfer to arena (must-use) |
#### Capacity
| `reserve` | `fn reserve(&mut self, additional: usize)` | Reserve (may over-allocate) |
| `reserve_exact` | `fn reserve_exact(&mut self, additional: usize)` | Reserve exactly |
| `try_reserve` | `fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>` | Fallible |
| `try_reserve_exact` | `fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError>` | Fallible exact reserve |
| `capacity` | `fn capacity(&self) -> usize` | Total slots |
#### Introspection
| `len` | `fn len(&self) -> usize` |
| `is_empty` | `fn is_empty(&self) -> bool` |
| `capacity` | `fn capacity(&self) -> usize` |
| `as_slice` | `fn as_slice(&self) -> &[T]` |
| `as_mut_slice` | `fn as_mut_slice(&mut self) -> &mut [T]` |
| `iter` | `fn iter(&self) -> Iter<'_, T>` |
| `iter_mut` | `fn iter_mut(&mut self) -> IterMut<'_, T>` |
| `get` | `fn get(&self, i: usize) -> Option<&T>` |
| `get_mut` | `fn get_mut(&mut self, i: usize) -> Option<&mut T>` |
| `first` / `first_mut` | `fn first(&self) -> Option<&T>` (and `_mut`) |
| `last` / `last_mut` | `fn last(&self) -> Option<&T>` (and `_mut`) |
| `contains` | `fn contains(&self, x: &T) -> bool where T: PartialEq` |
#### Trait Implementations
`ArenaVec<T>` implements:
- `Deref<Target = [T]>` and `DerefMut` — transparent slice access. All
slice methods (`sort`, `binary_search`, `chunks`, …) work directly.
- `AsRef<[T]>`, `AsMut<[T]>`, `Borrow<[T]>`, `BorrowMut<[T]>`.
- `Index<usize>`, `IndexMut<usize>`.
- `Extend<T>`, `IntoIterator` (owned, `&`, `&mut`).
- `PartialEq`, `PartialEq<[T]>`, `PartialEq<[T; N]>`, `Eq`, `Hash`,
`Debug`.
```rust
use fastarena::{Arena, ArenaVec};
let mut arena = Arena::new();
let mut v = ArenaVec::new(&mut arena);
v.extend_exact([3u32, 1, 4, 1, 5, 9, 2, 6]);
v.sort(); // direct slice method via Deref
v.dedup(); // ArenaVec helper
assert_eq!(&*v, &[1, 2, 3, 4, 5, 6, 9]);
```
#### Truncate / Resize
Shorten or grow the vector in-place:
```rust
let mut v = ArenaVec::new(&mut arena);
v.extend_exact([1u32, 2, 3, 4, 5]);
v.truncate(3); // [1, 2, 3] — elements 4, 5 dropped
v.resize(6, 0); // [1, 2, 3, 0, 0, 0] — fills with clone
v.resize(2, 0); // [1, 2] — excess dropped
```
#### The `finish()` Semantic
This is the key differentiator from `bumpalo::collections::Vec`:
```rust
// finish() — ArenaVec consumed, slice lives as long as the arena.
// The returned reference is #[must_use].
let slice: &mut [u32] = {
let mut v = ArenaVec::new(&mut arena);
v.push(1); v.push(2); v.push(3);
v.finish()
};
// drop without finish() — element destructors run immediately
{
let mut v = ArenaVec::new(&mut arena);
v.push(String::from("hello"));
} // String::drop() fires here; arena retains backing memory
```
**Drop behaviour after `finish()`:**
- **Without `drop-tracking`** — destructors are not called. The slice may
dangle if the arena is reset / rewound.
- **With `drop-tracking`** — `finish()` registers the slice with the
arena's drop registry; element destructors run in LIFO order on the
next `reset` / `rewind`.
#### Inside Transactions
Use `txn.arena_mut()` to create an `ArenaVec` within a transaction:
```rust
let mut txn = arena.transaction();
let slice = {
let mut v = ArenaVec::new(txn.arena_mut());
v.extend_exact([1u32, 2, 3]);
v.finish()
};
txn.commit(); // slice survives
```
#### `arenavec!` Macro
The `arenavec!` macro provides concise `ArenaVec` construction. The arena
reference always follows the `in` keyword.
**Syntax:**
```text
arenavec![in &mut arena] // empty
arenavec![in &mut arena; 1, 2, 3] // list of elements
arenavec![in &mut arena; 0u32; 10] // repeated element (requires T: Clone)
```
**Empty:**
```rust
use fastarena::{Arena, ArenaVec, arenavec};
let mut arena = Arena::new();
let v: ArenaVec<u32> = arenavec![in &mut arena];
assert!(v.is_empty());
```
**List of elements:**
```rust
use fastarena::{Arena, arenavec};
let mut arena = Arena::new();
let v = arenavec![in &mut arena; 1u32, 2, 3];
assert_eq!(v.as_slice(), &[1, 2, 3]);
```
**Repeated element** (requires `T: Clone`):
```rust
use fastarena::{Arena, arenavec};
let mut arena = Arena::new();
let v = arenavec![in &mut arena; 0u32; 10];
assert_eq!(v.len(), 10);
assert_eq!(v.as_slice(), &[0; 10]);
```
**With `finish()`:**
```rust
use fastarena::{Arena, arenavec};
let mut arena = Arena::new();
let slice = arenavec![in &mut arena; 1u32, 2, 3].finish();
assert_eq!(slice, &mut [1, 2, 3]);
```
**Inside transactions:**
```rust
use fastarena::{Arena, arenavec};
let mut arena = Arena::new();
let mut txn = arena.transaction();
{
let v = arenavec![in txn.arena_mut(); 10u32, 20, 30];
assert_eq!(v.as_slice(), &[10, 20, 30]);
}
txn.commit();
```
---
### ArenaStats
O(1) memory usage snapshot.
```rust
pub struct ArenaStats {
pub bytes_allocated: usize, // In-use bytes
pub bytes_reserved: usize, // Total reserved across all blocks
pub block_count: usize, // Number of blocks
}
impl ArenaStats {
pub fn utilization(&self) -> f64; // bytes_allocated / bytes_reserved
pub fn bytes_idle(&self) -> usize; // bytes_reserved - bytes_allocated
}
```
```rust
let stats = arena.stats();
println!("{:.1}% utilized", stats.utilization() * 100.0);
println!("{} bytes idle", stats.bytes_idle());
```
---
### Checkpoint
Opaque snapshot of arena position. `Copy` — zero-cost to pass around.
```rust
#[derive(Debug, Clone, Copy)]
pub struct Checkpoint { /* opaque */ }
```
Implements `Display` — prints `Checkpoint(block=N, offset=N, bytes=N)`.
**Usage:**
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let x = arena.alloc(10u64);
let cp = arena.checkpoint(); // O(1) snapshot — copies 3 integers
arena.alloc(20u64);
arena.alloc(30u64);
// arena now holds [10, 20, 30]
arena.rewind(cp); // 20 and 30 gone; blocks retained for reuse
assert_eq!(*x, 10); // pre-checkpoint data still valid
// Checkpoint is Copy — pass it around freely
let cp2 = arena.checkpoint();
let cp3 = cp2; // no cost
arena.alloc(40u64);
arena.rewind(cp3); // 40 gone
// Print checkpoint info
let cp = arena.checkpoint();
println!("{cp}"); // e.g. "Checkpoint(block=0, offset=8, bytes=8)"
```
**Speculative work pattern:**
```rust
use fastarena::Arena;
let mut arena = Arena::new();
arena.alloc(1u64);
let cp = arena.checkpoint();
// Try some work — may fail
let success = false;
arena.alloc(2u64);
arena.alloc(3u64);
if !success {
arena.rewind(cp); // undo speculative allocations, keep original
}
```
---
### TxnStatus
Outcome of `Transaction::commit` or `Transaction::rollback`.
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TxnStatus {
Committed,
RolledBack,
}
```
Implements `Display` — prints `"committed"` or `"rolled back"`.
**Usage:**
```rust
use fastarena::{Arena, TxnStatus};
let mut arena = Arena::new();
let mut txn = arena.transaction();
txn.alloc(42u32);
let status = txn.commit();
assert_eq!(status, TxnStatus::Committed);
println!("{status}"); // "committed"
// Rollback returns RolledBack
let mut txn = arena.transaction();
txn.alloc(1u32);
let status = txn.rollback();
assert_eq!(status, TxnStatus::RolledBack);
println!("{status}"); // "rolled back"
```
---
### TxnDiff
Allocation metrics for a single transaction scope, returned by `Transaction::diff`.
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TxnDiff {
pub bytes_allocated: usize, // bytes allocated since txn opened
pub blocks_touched: usize, // blocks written to during txn
}
```
Implements `Display` — prints `"N bytes in M block(s)"`.
**Usage:**
```rust
use fastarena::Arena;
let mut arena = Arena::new();
let mut txn = arena.transaction();
let diff = txn.diff();
assert_eq!(diff.bytes_allocated, 0);
assert_eq!(diff.blocks_touched, 0);
txn.alloc(100u64);
txn.alloc_slice(vec![0u8; 256]);
let diff = txn.diff();
assert!(diff.bytes_allocated > 0);
assert_eq!(diff.blocks_touched, 1);
println!("{diff}"); // e.g. "264 bytes in 1 block(s)"
txn.commit();
```
---
### TryReserveError
Error returned by `ArenaVec::try_reserve` and `ArenaVec::try_reserve_exact`.
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TryReserveError {
CapacityOverflow, // requested size exceeds usize::MAX
AllocError, // arena is out of memory
}
```
Implements `Display` and `std::error::Error`.
**Usage:**
```rust
use fastarena::{Arena, ArenaVec, TryReserveError};
let mut arena = Arena::with_capacity(64);
let mut v = ArenaVec::new(&mut arena);
match v.try_reserve_exact(usize::MAX) {
Ok(()) => unreachable!(),
Err(TryReserveError::CapacityOverflow) => println!("overflow"),
Err(TryReserveError::AllocError) => println!("oom"),
}
```
---
### ArenaVecIntoIter
Owning iterator over an `ArenaVec`. Returned by `ArenaVec::into_iter()` (i.e. `for x in vec`). Drains elements front-to-back; drops remaining elements in LIFO order when the iterator is dropped.
```rust
pub struct ArenaVecIntoIter<'arena, T> { /* opaque */ }
impl<T> Iterator for ArenaVecIntoIter<'_, T> { type Item = T; }
impl<T> ExactSizeIterator for ArenaVecIntoIter<'_, T> {}
```
**Usage:**
```rust
use fastarena::{Arena, ArenaVec};
let mut arena = Arena::new();
let v: ArenaVec<u32> = {
let mut v = ArenaVec::new(&mut arena);
v.extend_exact([10, 20, 30, 40, 50]);
v
};
// for loop consumes the ArenaVec via IntoIterator
let mut collected = Vec::new();
for val in v {
collected.push(val);
}
assert_eq!(collected, vec![10, 20, 30, 40, 50]);
// size_hint is exact
let mut v2 = ArenaVec::new(&mut arena);
v2.extend_exact([1, 2, 3]);
let iter = v2.into_iter();
assert_eq!(iter.size_hint(), (3, Some(3)));
assert_eq!(iter.len(), 3);
```
---
## Performance
### Head-to-Head: fastarena vs bumpalo vs typed-arena
| alloc 1k items | **894 ns** | 937 ns | 1072 ns |
| alloc_slice n=64 | **10 ns** | 53 ns | 84 ns |
| alloc_slice n=1024 | **64 ns** | 518 ns | — |
| alloc_str (100x) | 202 ns | **190 ns** | — |
| ArenaVec n=16 | **30 ns** | 42 ns | 34 ns |
| ArenaVec n=256 | **263 ns** | 346 ns | 516 ns |
| ArenaVec n=4096 | **3.4 µs** | 8.5 µs | 11.1 µs |
| 10k allocs + reset | **15.0 µs** | 15.1 µs | 2.8 µs† |
| reset (1 block) | **20 ns** | 696 ns | — |
| 128 KB alloc | 63 ns | **27 ns** | — |
† typed-arena drops and re-creates the arena each iteration; not directly comparable.
### Fast Path vs Box/Vec
| alloc 1k u64 | **864 ns** | 15732 ns | **18x** |
| alloc_slice n=512 | **59 ns** | 65 ns | ~1x |
| alloc_slice n=4096 | **246 ns** | 236 ns | ~1x |
| 10k allocs + reset | **15.5 µs** | 211.8 µs | **14x** |
| `Arena::new` | **22 ns** | — | — |
| `checkpoint()` | **93 ns** | — | — |
| `reset` 1 block | **20 ns** | — | — |
| `commit` 16 allocs | **1.3 µs** | — | — |
### Why fastarena excels
- **5-8x faster slice allocation** than bumpalo (batch write in tight loop)
- **2-3x faster ArenaVec** than bumpalo/typed-arena for bulk collection building
- **Tied on alloc** — on par with bumpalo for single-item allocation
- **14x faster than Box** for bulk alloc + reclaim cycles
- **35x faster reset** than bumpalo for single-block arenas (O(peak) block reuse)
- **Zero dependencies**: No external crates required
### Complexity
| `alloc` (fast path) | O(1) |
| `alloc` (new block) | O(1) amortized |
| `checkpoint` | O(1) |
| `rewind` | O(k) where k ≈ 0–1 |
| `reset` | O(p) where p = peak blocks since last reset |
| `stats` | O(1) |
---
## Best Practices
### 1. Pre-allocate for Known Workloads
```rust
// Avoids early block chaining
let arena = Arena::with_capacity(1024 * 1024);
```
### 2. Reset Over Recreate
```rust
// Good: warm pages, no OS calls, O(peak_blocks) — all capacity reused
let mut arena = Arena::new();
for batch in batches {
process(&mut arena, batch);
arena.reset(); // zeros only blocks used since last reset
}
```
`reset()` tracks a `high_water_mark` internally — it only touches blocks
that were actually used, leaving untouched blocks' offsets at zero.
All memory is fully reusable with zero waste.
### 3. Use Transactions for Fallible Operations
```rust
arena.with_transaction(|txn| {
let parsed = parse(txn, input)?;
let optimized = optimize(txn, parsed)?;
Ok(optimized)
}); // auto-rollback on any Err
```
### 4. Use `finish()` When Building Then Freezing
```rust
let slice: &mut [u32] = {
let mut v = ArenaVec::new(&mut arena);
for i in 0..n { v.push(compute(i)); }
v.finish() // arena owns it now
};
```
### 5. Set Budgets in Server Contexts
```rust
let mut txn = arena.transaction();
txn.set_limit(64 * 1024); // 64 KB per request
handle_request(&mut txn);
txn.commit();
```
---
## Feature Flags
| `drop-tracking` | Off | Run destructors in LIFO order on `reset`/`rewind` |
```toml
# Default (zero-cost, no destructor calls)
fastarena = "0.2"
# With drop-tracking
fastarena = { version = "0.2", features = ["drop-tracking"] }
```
---
## Safety Considerations
### Lifetime Guarantees
Arena-allocated references are invalidated by `reset()` / `rewind()`:
```rust
let mut arena = Arena::new();
let x = arena.alloc(42);
arena.reset();
// x is now dangling — UB if used
```
### No Thread Safety
`Arena` is `!Send` + `!Sync`. For multi-threaded use:
```rust
// Thread-local (recommended for request-scoped workloads)
thread_local! {
static ARENA: RefCell<Arena> = RefCell::new(Arena::new());
}
// Or wrap in Mutex (adds contention)
let arena = Mutex::new(Arena::new());
```
### `finish()` vs `drop()`
- `finish()` — destructors **not** called; arena owns the data
- `drop()` without `finish()` — destructors run immediately; arena retains memory
---
## Minimum Supported Rust Version
**Rust 1.66.0**