arena-lang 1.0.0

Typed bump/arena allocation for AST and IR nodes with stable addresses.
Documentation

Installation

[dependencies]
arena-lang = "1"

Or from the terminal:

cargo add arena-lang

Usage

Allocate a value into the arena and get back a stable, Copy handle. The handle — not a raw pointer — is the stable address, so a tree of nodes is wired by handle and never tangles the borrow checker.

use arena_lang::{Arena, Id};

enum Expr {
    Int(i64),
    Add(Id<Expr>, Id<Expr>),
}

let mut arena = Arena::new();
let one = arena.alloc(Expr::Int(1));
let two = arena.alloc(Expr::Int(2));
let sum = arena.alloc(Expr::Add(one, two)); // stores handles to its children

// Child handles still resolve after the parent was allocated.
if let Some(Expr::Add(l, r)) = arena.get(sum) {
    assert!(matches!(arena.get(*l), Some(Expr::Int(1))));
    assert!(matches!(arena.get(*r), Some(Expr::Int(2))));
}

Back-patch a node after it exists, and walk every node without following the edges:

use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.alloc(0_u32);
if let Some(slot) = arena.get_mut(id) {
    *slot = 99;
}
assert_eq!(arena.get(id), Some(&99));

let total: u32 = arena.iter().map(|(_, v)| *v).sum();
assert_eq!(total, 99);

The fallible path returns a defined error instead of panicking at the u32::MAX node ceiling:

use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.try_alloc("ok")?;
assert_eq!(arena.get(id), Some(&"ok"));
# Ok::<(), arena_lang::ArenaError>(())

See docs/API.md for the full reference.

How it works

An Arena<T> stores its values end to end in one contiguous buffer. Allocation appends a value and returns its position as a 32-bit Id<T>; resolving one is a single indexed lookup, constant time, no search. Because values are only appended — never moved or individually freed — a handle stays valid for the life of the arena and keeps resolving to the same value through every later allocation, so nodes can reference one another by handle and the tree never moves. The whole arena is released at once when it drops, which is the allocation pattern an AST or IR wants. The handle is four bytes whatever it points at, so the space addressed by a single arena is capped at u32::MAX values; overrunning it is a defined ArenaError, never a silent wrap.

Status

v1.0.0stable. The full surface is in place: the typed Arena<T>, the four-byte Copy Id<T> handle, and the fallible try_alloc path, each invariant property-tested against a Vec-backed reference arena. The public API is frozen under Semantic Versioning — no breaking change before 2.0; see the ROADMAP.

Contributing

See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.