arena-lang 1.0.0

Typed bump/arena allocation for AST and IR nodes with stable addresses.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# arena-lang — API Reference

> Complete reference for every public item in `arena-lang`, with examples.
> **Status: stable (1.0).** The surface below is frozen under
> [Semantic Versioning]https://semver.org: within the `1.x` series no public item
> is removed or changed incompatibly. New items may still arrive as minor releases;
> any breaking change waits for `2.0`. See [`dev/ROADMAP.md`]../dev/ROADMAP.md.

## Table of Contents

- [Overview]#overview
- [Installation]#installation
- [Quick start]#quick-start
- [The model]#the-model
- [`Arena`]#arena
  - [`Arena::new`]#arenanew
  - [`Arena::with_capacity`]#arenawith_capacity
  - [`Arena::reserve`]#arenareserve
  - [`Arena::alloc`]#arenaalloc
  - [`Arena::try_alloc`]#arenatry_alloc
  - [`Arena::get`]#arenaget
  - [`Arena::get_mut`]#arenaget_mut
  - [`Arena::contains`]#arenacontains
  - [`Arena::len` / `is_empty` / `capacity`]#arenalen--is_empty--capacity
  - [`Arena::iter`]#arenaiter
- [`Id`]#id
- [`ArenaError`]#arenaerror
- [Feature flags]#feature-flags

---

## Overview

arena-lang is the allocation floor a tree of compiler nodes is built on. It offers
one focused, append-only surface: allocate a value into an [`Arena`](#arena) and get
back an [`Id`](#id) — a four-byte, `Copy`, type-tagged handle that stays valid for the
life of the arena.

The handle, not a raw pointer, is the stable address: a node stores `Id`s pointing
at its children, so a tree is wired by handle and never tangles the borrow checker.
Values are never freed individually; the whole arena is released at once when it is
dropped. It owns typed allocation and stable addressing only — no tree shape, no
traversal, no parsing.

---

## Installation

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

Or from the terminal:

```bash
cargo add arena-lang
```

The crate is `no_std`-friendly: it needs `alloc` but not the full standard library.
Disable the default `std` feature for a `no_std` build.

---

## Quick start

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

---

## The model

An `Arena<T>` holds many values of one type. [`alloc`](#arenaalloc) appends a value
and returns an [`Id<T>`](#id); [`get`](#arenaget) resolves a handle back to the
value in a single slot lookup — constant time, no search. A handle stays valid for
the life of the arena and keeps resolving to the same value through every later
allocation, so a node can hold handles to other nodes and the graph never moves.

The arena is append-only: there is no per-value free, which is the allocation
pattern an AST or IR wants — nodes are created forward during a pass and released
together when the arena drops. Handles are addressed by a 32-bit slot counter, so an
arena holds up to `u32::MAX` values over its lifetime; reaching that ceiling is the
[`ArenaError`](#arenaerror) reported by [`try_alloc`](#arenatry_alloc).

---

## `Arena`

`Arena<T>` is the type you construct, allocate into, and query. It implements
`Default` (equivalent to [`new`](#arenanew)) and `Debug` (which prints the arena's
length and capacity, not its contents, so it carries no `T: Debug` bound).

```rust
use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.alloc("root");
assert_eq!(arena.get(id), Some(&"root"));
assert_eq!(arena.len(), 1);
```

### `Arena::new`

```rust
pub const fn new() -> Arena<T>
```

Creates an empty arena. `const`, so it can initialise a `static` or `const`.

```rust
use arena_lang::Arena;

let arena: Arena<u32> = Arena::new();
assert!(arena.is_empty());
```

### `Arena::with_capacity`

```rust
pub fn with_capacity(capacity: usize) -> Arena<T>
```

Creates an empty arena with room for `capacity` values preallocated.

**Parameters**

- `capacity` — the number of values to reserve backing storage for. A hint only:
  the first `capacity` allocations will not reallocate. The arena still starts
  empty.

```rust
use arena_lang::Arena;

let mut arena = Arena::with_capacity(3);
let ids = [arena.alloc('a'), arena.alloc('b'), arena.alloc('c')];
assert_eq!(ids.map(|id| *arena.get(id).unwrap()), ['a', 'b', 'c']);
```

### `Arena::reserve`

```rust
pub fn reserve(&mut self, additional: usize)
```

Reserves capacity for at least `additional` more values, folding several
incremental growths into one before a burst of allocations.

**Parameters**

- `additional` — the number of further values to make room for.

```rust
use arena_lang::Arena;

let mut arena: Arena<u8> = Arena::new();
arena.reserve(128);
assert!(arena.capacity() >= 128);
```

### `Arena::alloc`

```rust
pub fn alloc(&mut self, value: T) -> Id<T>
```

Allocates `value` and returns a stable [`Id`](#id) handle. This is the hot path; the
handle is valid for the life of the arena.

**Parameters**

- `value` — the value to store. Ownership moves into the arena.

**Panics**

Panics only if the arena has already allocated `u32::MAX` values — a ceiling of more
than four billion live nodes, unreachable for any real tree. Use
[`try_alloc`](#arenatry_alloc) for an explicit non-panicking path.

```rust
use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.alloc(42);
assert_eq!(arena.get(id), Some(&42));
```

Wiring a node to its children by handle:

```rust
use arena_lang::{Arena, Id};

struct Node { value: i32, next: Option<Id<Node>> }

let mut arena = Arena::new();
let tail = arena.alloc(Node { value: 2, next: None });
let head = arena.alloc(Node { value: 1, next: Some(tail) });

let first = arena.get(head).unwrap();
assert_eq!(first.value, 1);
assert_eq!(arena.get(first.next.unwrap()).unwrap().value, 2);
```

### `Arena::try_alloc`

```rust
pub fn try_alloc(&mut self, value: T) -> Result<Id<T>, ArenaError>
```

The non-panicking counterpart to [`alloc`](#arenaalloc): identical on success, but
returns [`ArenaError::CapacityExhausted`](#arenaerror) instead of panicking at the
`u32::MAX`-value ceiling. Prefer it when building a tree from input whose size you do
not control.

**Parameters**

- `value` — the value to store.

**Errors**

Returns [`ArenaError::CapacityExhausted`](#arenaerror) when the arena's slot space is
full; the arena is left unchanged.

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

### `Arena::get`

```rust
pub fn get(&self, id: Id<T>) -> Option<&T>
```

Borrows the value behind `id`, or `None` if the handle does not name a live value in
this arena. A direct slot lookup, not a search; the `None` case guards an
out-of-range handle so resolution never reads outside the arena's storage.

**Parameters**

- `id` — a handle from [`alloc`]#arenaalloc / [`try_alloc`]#arenatry_alloc.

```rust
use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.alloc(vec![1, 2, 3]);
assert_eq!(arena.get(id).map(Vec::len), Some(3));
```

### `Arena::get_mut`

```rust
pub fn get_mut(&mut self, id: Id<T>) -> Option<&mut T>
```

Mutably borrows the value behind `id`, for back-patching a node after it is
allocated — resolving a forward reference, or filling in a parent link.

**Parameters**

- `id` — a handle into this arena.

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

### `Arena::contains`

```rust
pub fn contains(&self, id: Id<T>) -> bool
```

Returns `true` if `id` names a live value in this arena.

**Parameters**

- `id` — a handle to test.

```rust
use arena_lang::Arena;

let mut arena = Arena::new();
let id = arena.alloc("x");
assert!(arena.contains(id));
```

### `Arena::len` / `is_empty` / `capacity`

```rust
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool
pub fn capacity(&self) -> usize
```

The number of values in the arena, whether it holds none, and how many it can hold
before it must grow. Because values are never removed, `len` only grows and equals
the number of handles the arena has issued.

```rust
use arena_lang::Arena;

let mut arena = Arena::with_capacity(4);
assert!(arena.is_empty());
arena.alloc(());
assert_eq!(arena.len(), 1);
assert!(arena.capacity() >= 4);
```

### `Arena::iter`

```rust
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)>
```

Iterates over every value in the arena, paired with its handle. Values are visited in
allocation order — the order their ids were minted — so the first pair is
`(Id 0, first value)`. Useful for a pass that walks all nodes without following the
tree's edges.

```rust
use arena_lang::Arena;

let mut arena = Arena::new();
let a = arena.alloc(10);
let b = arena.alloc(20);

// Allocation order, with the matching handles.
let pairs: Vec<_> = arena.iter().collect();
assert_eq!(pairs, vec![(a, &10), (b, &20)]);

let total: i32 = arena.iter().map(|(_, v)| *v).sum();
assert_eq!(total, 30);
```

---

## `Id`

A small, copyable, type-tagged handle to one value in an [`Arena`](#arena). It is a
single `u32` — four bytes, the same as a bare index, for **every** element type — so
passing one is no more expensive than passing an integer. It stays valid for the life
of the arena that issued it.

The `T` tag is compile-time only and occupies no space: it stops an `Id<Expr>` from
being passed where an `Id<Stmt>` is expected. `Id<T>` is `Copy`, `Eq`, `Ord`, and
`Hash` for **every** `T` — the tag never adds a trait bound — so it works as a
`HashMap` / `BTreeMap` key regardless of what it points at. There is no public
constructor: an `Id` can only come from an [`Arena::alloc`](#arenaalloc).

```rust
use arena_lang::{Arena, Id};
use std::collections::HashMap;

let mut arena = Arena::new();
let a = arena.alloc("alpha");
let b = arena.alloc("beta");

// Copy, four bytes, and usable as a map key.
let mut labels: HashMap<Id<&str>, u32> = HashMap::new();
labels.insert(a, 1);
labels.insert(b, 2);
assert_eq!(labels[&a], 1);
assert_ne!(a, b);
assert_eq!(core::mem::size_of_val(&a), 4);
```

---

## `ArenaError`

```rust
#[non_exhaustive]
pub enum ArenaError {
    CapacityExhausted,
}
```

The reason a value could not be allocated, returned by
[`try_alloc`](#arenatry_alloc). The enum is `#[non_exhaustive]`. It implements
`core::error::Error` and `Display`.

**`CapacityExhausted`** — the arena's slot space is full: it already holds `u32::MAX`
values and cannot represent another handle. Unreachable for any realistic tree (more
than four billion live nodes), but reported rather than ignored so the limit is a
defined boundary, never a silent wrap.

```rust
use arena_lang::ArenaError;

assert_eq!(
    ArenaError::CapacityExhausted.to_string(),
    "arena is full: cannot allocate beyond u32::MAX values",
);
```

---

## Feature flags

| Feature | Default | Description |
|---------|---------|-------------|
| `std`   | yes     | Links the standard library. The crate needs only `alloc`, so this is opt-out: disabling it compiles `arena-lang` under `#![no_std]` with no loss of function. |

Disabling `std` keeps the crate `no_std`:

```toml
[dependencies]
arena-lang = { version = "1", default-features = false }
```

---

<sub>Copyright &copy; 2026 <strong>James Gober</strong>.</sub>