std.arena
std.arena provides Arena<T>, a generational arena that owns its entries and hands out copyable, non-owning Handle<T> identities.
Contract
Arena<T>owns every live entry and drops remaining entries when the arena is dropped or cleared.addmoves a value into the arena and may allocate; allocation failure is reported asAllocError.- Under RFC 0064,
addis the exact verb because insertion creates one non-positional member and mints itsHandle;removeselects that member by handle and returns its exact owner. There are noinsert,put,take,delete, orerasealiases. Handle<T>values are copyable identities, not owners. They do not keep entries alive and are validated only by the owning arena.view,get,remove, andcontainsdo not allocate. Failed lookup is reported withError.StaleorError.Missingwhere the method is fallible.- Slot indexes, generation values, reuse order, and physical storage layout are unspecified.
Handle<T>
Handle<T> is a compiler-reserved special type, like Unique<T>. It is:
- opaque — represented internally as an index plus a generation; you cannot read or construct the fields.
handle(...)is a compile error: handles are minted only by an owner such asarena.add. Copy— copying a handle is free and does not keep the target alive.- non-owning — a handle never drops or extends the lifetime of its target.
Looking a handle up through its owning Arena<T> validates both presence and generation, so a handle to a removed (or reused) slot is rejected instead of silently aliasing a different value.
Arena<T>
import std.arena.{ Arena };
var arena = Arena<i32>();
let a = try arena.add(10);
let b = try arena.add(20);
assert (try arena.view(a)) == 10; // shared borrow
(try arena.get(b)) = 21; // mutable borrow (inline)
let taken = try arena.remove(a); // moves the value out; `a` is now stale
assert taken == 10;
assert !arena.contains(a);Errors
export error Error { Stale, Missing }Stale— the slot is in range but its generation has advanced (the entry was removed, and possibly the slot reused by a newer occupant).Missing— the handle does not name any entry, for example afterclear.
Methods
| Method | Signature | Notes |
|---|---|---|
add | add(value: ^T) throws(AllocError) -> Handle<T> | Moves value in; reuses freed slots. |
view | view(id: Handle<T>) throws(Error) -> T | Read-only borrow tied to the arena. |
get | get(id: Handle<T>) throws(Error) -> &T | Mutable borrow tied to the arena. |
remove | remove(id: Handle<T>) throws(Error) -> ^T | Returns ownership of the value; bumps the slot generation. |
contains | contains(id: Handle<T>) -> bool | True when the handle still names a live entry. |
clear | clear() | Drops every entry and resets the arena; existing handles become Missing. |
length | @readonly var length: usize | Number of live entries. |
Borrows and invalidating mutation
References returned by view/get are ordinary checked borrows tied to the arena owner. A held mutable borrow conflicts with any arena mutation that could invalidate slots:
let r = try arena.get(a);
let c = try arena.add(99); // error: `arena` is already mutably borrowed
r = 5;As with other containers, bind-and-write through a mutable borrow is rejected; mutate inline instead: (try arena.get(a)) = 5;.
Stable handles across moves
Handles remain valid when the Arena<T> itself is moved (the entries live in a heap allocation the arena owns), so passing an arena by value preserves every handle minted from it.
Accepted Sparse-Storage Target
The checked-in arena implementation is transitional. The accepted target has exactly the public Arena<T>() constructor shown above and always allocates through the statically known built-in process-allocation domain. The current custom-allocator constructor and stored allocator callback value are removed without an alias; a custom-allocation arena would require a separate nominal type accepted by a future RFC.
Arena<T> does not represent its hole-bearing slot area as ^[T], [T], or an OutputSpan<T>. It owns one SDK-internal affine RawStorage<T> allocation for the slots. RawStorage<T> knows its slot capacity and layout, but does not know which slots contain live values and never scans or drops T values by itself. It has no public constructor, methods, fields, import path, or layout contract.
The arena's fully initialized generations, occupied, and free_slots metadata remain ordinary dense owning storage. Every metadata element up to capacity is initialized; slot_count and free_count describe meaningful logical prefixes rather than holes in those arrays. occupied[index] is the sole authoritative proof that slot index contains a live T. RawStorage has no second bitmap, typestate-token collection, or independent liveness state.
Insertion initializes the complete value before publishing occupied[index] = 1. Removal transfers exactly one owner out and disarms the occupancy state before the owner is returned. Destruction disarms occupancy before invoking user cleanup. Those transition steps contain no checked error, callback, suspension, or other observable user code between the raw slot operation and the metadata update.
Growth is a container-level transaction, not RawStorage.grow or raw byte reallocation. The arena checks every size and alignment calculation and allocates the complete destination before touching its source. It then moves only occupied values; it never copies capacity bytes as though every slot held an initialized Copy value. After transfer starts there is no remaining checked edge. Failure during preparation leaves the old arena unchanged.
Zero-sized T values retain logical slots, occupancy, handles, and exactly-once cleanup without allocating payload bytes or performing element-pointer arithmetic. Metadata still exists when required. Slot addresses are not stable: handles survive growth because they contain index and generation, not because an entry stays at one address.
Related Modules
Use std.collections for dense arrays, maps, and sets. Use Arena<T> when stable non-owning identities are more important than positional indexes.
Status
std.arena is the accepted public generational arena API. Its SDK-internal sparse-storage representation and built-in-only allocation policy are accepted targets whose implementation is pending; the checked-in source still contains false owning slices over holes and a custom-allocator constructor. For the exact currently exported transitional surface, see Standard Library Inventory.