🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Arena Patterns

An arena allocator is an allocator that frees every allocation at once when the arena is destroyed. It is a good fit when many short-lived values share one lifetime.

One arena, two values

The arena hands out both slices, and main frees neither by hand.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    const first = try alloc.alloc(u8, 3);
    first[0] = 1;
    first[1] = 2;
    first[2] = 3;
    const second = try alloc.alloc(u8, 2);
    second[0] = 10;
    second[1] = 20;
    try out.print("first={d} second={d}\n", .{ first.len, second.len });
}

Many values, one free

Five slices come from one arena, and a single defer frees them all.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    var total: usize = 0;
    var i: usize = 0;
    while (i < 5) : (i += 1) {
        const piece = try alloc.alloc(u8, 4);
        total += piece.len;
    }
    try out.print("pieces=5 total={d}\n", .{total});
}

Reset and reuse

Reset in free-all mode frees every allocation, and the arena serves new ones after.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    const a = try alloc.alloc(u8, 4);
    a[0] = 3;
    try out.print("a={d}\n", .{a[0]});
    _ = arena.reset(.free_all);
    const b = try alloc.alloc(u8, 4);
    b[0] = 9;
    try out.print("b={d}\n", .{b[0]});
}

Example

const std = @import("std");

fn greeting(alloc: std.mem.Allocator, name: []const u8) ![]u8 {
    return std.fmt.allocPrint(alloc, "Hello, {s}!", .{name});
}

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    const g = try greeting(alloc, "Zig");
    const h = try greeting(alloc, "Arena");
    try out.print("{s} {s}\n", .{ g, h });
}

Key points

  • ArenaAllocator: an arena allocator is an allocator that keeps every allocation until the whole arena is freed.
  • Backing allocator: a backing allocator is the allocator an arena asks for fresh memory.
  • A single deferred arena deinit frees every value the arena handed out, with no free call per value.
  • Lifetime: a lifetime is the span of time a value stays valid.
  • Values from one arena share one lifetime, so none of them may be used after the arena is destroyed.
  • Calling reset in free-all mode frees every allocation and keeps the arena usable.
  • An arena fits short-lived work like building one report, where every piece dies together.

The helper asks the arena for each greeting. Main never frees either greeting by hand. The single defer frees both when main ends.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What frees every value handed out by an arena?