An arena allocator is used when many allocations have the same lifetime.
Instead of freeing each object one by one, the program frees the whole arena at once.
This is useful for parsers, compilers, request handlers, temporary buffers, and short-lived workspaces.
A small arena example:
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const a = try allocator.alloc(u8, 16);
const b = try allocator.alloc(u8, 32);
const c = try allocator.alloc(u8, 64);
@memset(a, 1);
@memset(b, 2);
@memset(c, 3);
std.debug.print("{d} {d} {d}\n", .{ a.len, b.len, c.len });
}There is no call to allocator.free(a), allocator.free(b), or allocator.free(c).
The memory is released here:
defer arena.deinit();The arena owns all memory allocated through arena.allocator().
This changes the cleanup model. Instead of many small cleanup points, there is one cleanup point.
That is the main reason to use an arena.
Consider a parser. It may allocate tokens, syntax nodes, strings, and temporary lists. If all of that memory is needed until parsing is finished, freeing each object separately adds noise.
An arena makes the ownership rule simple:
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// allocate parser data hereWhen the parser finishes, destroy the arena.
The parent allocator supplies memory to the arena:
std.heap.ArenaAllocator.init(std.heap.page_allocator)The arena asks the parent allocator for larger blocks, then serves smaller allocations from those blocks.
An arena is not a general replacement for a normal heap allocator.
It works best when memory dies as a group.
It works poorly when individual objects need to be freed at different times.
For example, this is a good arena use:
fn parseFile(parent_allocator: std.mem.Allocator, source: []const u8) !void {
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const tokens = try allocator.alloc(Token, 1024);
const nodes = try allocator.alloc(Node, 1024);
_ = source;
_ = tokens;
_ = nodes;
}The tokens and nodes are temporary. They are freed together.
This is a poor arena use:
const user = try allocator.create(User);
const session = try allocator.create(Session);
// later, user and session must be freed at different timesIf objects need separate lifetimes, use a general-purpose allocator or another structure.
The arena allocator may support individual free calls through the allocator interface, but code should not rely on arenas for precise per-object deallocation. The normal arena pattern is bulk release.
Arena allocation also helps avoid cleanup mistakes in error paths.
Without an arena:
const a = try allocator.alloc(u8, 100);
errdefer allocator.free(a);
const b = try allocator.alloc(u8, 200);
errdefer allocator.free(b);
const c = try allocator.alloc(u8, 300);
errdefer allocator.free(c);With an arena:
var arena = std.heap.ArenaAllocator.init(parent_allocator);
errdefer arena.deinit();
const allocator = arena.allocator();
const a = try allocator.alloc(u8, 100);
const b = try allocator.alloc(u8, 200);
const c = try allocator.alloc(u8, 300);One cleanup handles all successful allocations.
If the function succeeds and returns data allocated in the arena, ownership must be clear. The arena itself must outlive that data.
This is wrong:
fn makeName(parent_allocator: std.mem.Allocator) ![]u8 {
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const name = try allocator.dupe(u8, "zig");
return name; // wrong
}The returned slice points into arena memory. The arena is destroyed before the caller receives the slice.
To return arena-allocated data, return the arena owner too, or allocate the returned data from a longer-lived allocator.
The simple rule is:
Memory allocated from an arena must not outlive the arena.
Exercises:
Exercise 12-13. Create an arena allocator and allocate three byte slices from it. Do not free the slices individually.
Exercise 12-14. Rewrite a function with three temporary allocations so it uses an arena for cleanup.
Exercise 12-15. Explain why returning a slice allocated from a local arena is invalid.
Exercise 12-16. Write a function that receives a parent allocator, creates an arena, builds a temporary ArrayList(u8), prints it, and destroys the arena.