# arena-lang v0.2.0 — Core

**The core, front-loaded.** v0.2.0 turns the scaffold into a working arena: one
focused, append-only surface — allocate a value, get back a four-byte `Copy` handle
that stays valid for the life of the arena and keeps resolving to the same value
through every later allocation. This was the hard part of the roadmap, and the roadmap
put it first: a tree of AST or IR nodes references itself by handle, so if a handle
ever resolved to the wrong value, every pass that walks the tree would be wrong. It is
proven now, against a reference model, before anything is built on top of it.

## What is arena-lang?

The allocation floor a tree of compiler nodes is built on. It hands out a value's
storage from an arena, returns a stable, type-tagged handle that never goes stale for
the life of the arena, and frees everything at once when the arena is dropped. It is
the base-tier allocation crate beneath `ast-lang` and the IR — nodes allocated
forward, handles that stay put as the arena grows, no per-node deallocation. It owns
typed allocation and stable addressing only.

## What's new in 0.2.0

### `Arena<T>` — allocate forward, free at drop

The central type. [`alloc`](#) appends a value and returns an `Id<T>`; the arena is
append-only, which is exactly the allocation pattern an AST wants — many nodes
created during a pass, none removed, all released together when the arena drops.

```rust
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));

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))));
}
```

`get` resolves a handle in a single slot lookup — constant time, no search — and
`get_mut` allows back-patching a node after it is allocated, which is how a forward
reference or a parent link gets filled in once its target exists.

### `Id<T>` — a typed, `Copy` handle that is the stable address

Allocation returns an `Id<T>`: a handle that is a single `u32` — four bytes for any
element type, no more expensive to pass than an integer index. The handle, not a raw
pointer, is the stable address —
storing an `Id` in one node to point at another is how the tree is wired, and the
handle keeps resolving to the same value no matter how many later allocations grow
the arena.

The `T` tag is compile-time only and occupies no space; it stops an `Id<Expr>` from
being used where an `Id<Stmt>` is expected. Because the tag is purely a marker, the
handle is `Copy`, `Eq`, `Ord`, and `Hash` for *every* `T` — the tag never adds a
trait bound — so it works as a `HashMap` or `BTreeMap` key regardless of what it
points at. These trait impls are written by hand precisely so that a derive's implicit
`T: Clone` / `T: Eq` bounds never leak onto the handle.

### `try_alloc` and `ArenaError` — a defined ceiling, never a silent wrap

An arena addresses its slots with a 32-bit counter, so it holds up to `u32::MAX`
values over its lifetime. The convenience `alloc` documents that ceiling as its one
panic condition — unreachable at more than four billion live nodes, matching
`Vec::push`. `try_alloc` is the non-panicking counterpart: it returns
`ArenaError::CapacityExhausted` and leaves the arena unchanged, so a caller building a
tree from input it does not control can stop cleanly.

```rust
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>(())
```

### A contiguous backing, no dependencies

The arena is a contiguous `Vec<T>` and an `Id<T>` is the index into it, so resolution
is a bounds-checked slot read and allocation is an append. There is no generational
machinery and no per-node free, because an append-only arena needs neither — the
result is a four-byte handle and a backing with zero first-party dependencies, built
entirely on safe Rust under `#![forbid(unsafe_code)]`.

### Property tests against a `Vec`-backed reference

`tests/properties.rs` holds the invariants to a deliberately naive reference arena —
allocation is a push, resolution is an index — over a wide `proptest` input space:

- Every handle resolves to exactly the value the reference stored at the same step.
- Distinct allocations yield distinct handles; no two live values share one.
- A handle issued early keeps resolving through arbitrary later growth.
- `iter` visits every allocated value exactly once.

## Breaking changes

**None.** v0.1.0 had no public surface; everything here is additive.

## Verification

Run on Windows x86_64 (Rust stable and 1.85 MSRV); the same commands run on Linux
(WSL2 Ubuntu) and across the CI matrix on Linux, macOS, and Windows:

```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
cargo build --no-default-features
cargo +1.85 build --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo audit
cargo deny check
```

All green. Counts at this tag:

- Default features: 12 unit + 4 property + 16 doctests.
- `--all-features`: identical (serde adds no runtime surface yet).

## What's next

- **1.0.0 — API freeze.** The arena surface is stable and frozen until 2.0; only
  documentation, tests, and internal optimisation after that. `docs/API.md` marked
  stable, full test and benchmark suite green on all three platforms.

## Installation

```toml
[dependencies]
arena-lang = "0.2"
```

MSRV: Rust 1.85.

## Documentation

- [README](https://github.com/jamesgober/arena-lang/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/arena-lang/blob/main/docs/API.md)
- [CHANGELOG](https://github.com/jamesgober/arena-lang/blob/main/CHANGELOG.md)

---

**Full diff:** [`v0.1.0...v0.2.0`](https://github.com/jamesgober/arena-lang/compare/v0.1.0...v0.2.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/arena-lang/blob/main/CHANGELOG.md#020---2026-06-20).
