# Arena Allocator

### Arena Allocator

An arena allocator is an allocator that frees many allocations at once.

This is useful when several pieces of memory have the same lifetime.

For example, imagine a parser. It may allocate many small objects while reading a file:

```text
tokens
syntax nodes
temporary strings
metadata
```

You usually do not want to free each object one by one. You want to keep all of them while parsing, then free everything when parsing is done.

That is the main idea of an arena:

```text
allocate many times
free once
```

#### A First Arena Example

An arena allocator needs a backing allocator. The backing allocator provides large blocks of memory. The arena then serves smaller allocations from those blocks.

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit();

    const allocator = arena.allocator();

    const name = try allocator.dupe(u8, "Ada");
    const city = try allocator.dupe(u8, "London");

    std.debug.print("{s} from {s}\n", .{ name, city });
}
```

Notice that there is no `allocator.free(name)` and no `allocator.free(city)`.

The memory is released here:

```zig
defer arena.deinit();
```

When the arena is deinitialized, all memory allocated from it is released together.

#### Why Arenas Are Useful

Arenas are useful when memory has a simple lifetime.

Suppose you read one configuration file. During parsing, you allocate keys, values, arrays, and temporary structures.

After parsing, you may either keep the final result or discard everything.

If everything allocated during parsing belongs to the parsing step, an arena works well:

```text
create arena
parse file
use parsed data
destroy arena
```

This is easier than tracking hundreds of individual frees.

#### Arena Allocation Is Fast

An arena is often fast because it does not need to manage each allocation separately.

A general purpose allocator must support complex behavior:

```text
allocate A
allocate B
free A
allocate C
free B
free C
```

An arena has a simpler pattern:

```text
allocate A
allocate B
allocate C
free all at once
```

Because the pattern is simpler, the allocator can be simpler.

This does not mean arenas are always faster in every program, but they are often efficient for temporary data.

#### The Main Tradeoff

The main tradeoff is that individual frees usually do not matter.

If you call `free` on memory from an arena, the arena may not return that specific piece of memory immediately. The normal way to release arena memory is to destroy or reset the whole arena.

This means arenas can use more memory if you keep allocating for a long time.

Bad use:

```text
create one arena when the server starts
use it for every request forever
never reset it
```

That arena will keep growing.

Better use:

```text
create an arena for one request
handle the request
destroy the arena
```

Or:

```text
create an arena for one compiler pass
run the pass
destroy the arena
```

An arena should match a clear lifetime.

#### Request-Scoped Memory

A common use is request-scoped memory in a server.

Each request gets its own arena:

```zig
const std = @import("std");

fn handleRequest(parent_allocator: std.mem.Allocator) !void {
    var arena = std.heap.ArenaAllocator.init(parent_allocator);
    defer arena.deinit();

    const allocator = arena.allocator();

    const path = try allocator.dupe(u8, "/users/123");
    const response = try std.fmt.allocPrint(
        allocator,
        "requested path: {s}",
        .{path},
    );

    std.debug.print("{s}\n", .{response});
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    try handleRequest(gpa.allocator());
}
```

The request handler can allocate freely. When the request ends, `arena.deinit()` frees all request memory.

This keeps cleanup simple.

#### Arena-Owned Data Must Not Escape

The most important rule is this:

```text
Do not use arena memory after the arena is destroyed.
```

Look at this broken example:

```zig
const std = @import("std");

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, "Ada");

    return name;
}
```

This function returns `name`, but `name` was allocated from the arena. When the function returns, `arena.deinit()` runs. The returned slice points to memory that has already been freed.

That is a dangling slice.

The caller receives a slice, but the memory behind it is no longer valid.

A correct version should allocate returned memory from an allocator that outlives the function:

```zig
const std = @import("std");

fn makeName(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "Ada");
}
```

Or the arena must live outside the function:

```zig
const std = @import("std");

fn makeName(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "Ada");
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit();

    const name = try makeName(arena.allocator());

    std.debug.print("{s}\n", .{name});
}
```

Here, the arena lives until the end of `main`, so using `name` inside `main` is safe.

#### Using `deinit`

The simplest way to clean up an arena is:

```zig
defer arena.deinit();
```

This releases all memory owned by the arena.

A common structure is:

```zig
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit();

const allocator = arena.allocator();

// allocate many values
```

Keep this shape in mind. You will see it often in real Zig code.

#### Resetting an Arena

Sometimes you want to reuse the same arena object for multiple rounds of work.

For example:

```text
parse file 1
clear arena
parse file 2
clear arena
parse file 3
clear arena
```

In this case, you can reset the arena instead of destroying and recreating it each time.

Conceptually, resetting means:

```text
free the memory used by previous allocations
keep the arena ready for new allocations
```

The exact API can change across Zig versions, so check the standard library docs for your installed Zig version before using reset behavior in production code. The beginner habit is simpler: use `deinit` first, then learn reset when you need it.

#### Arena Allocator vs General Purpose Allocator

A general purpose allocator is flexible. It supports allocation and freeing in many different orders.

An arena allocator is specialized. It works best when many allocations die together.

| Allocator | Best For | Cleanup Style |
|---|---|---|
| General purpose allocator | Mixed lifetimes | Free each allocation |
| Arena allocator | Same lifetime | Free all at once |
| Fixed buffer allocator | Fixed memory limit | Free depends on usage |
| Page allocator | Page-level backing memory | Low-level cleanup |

The arena allocator is not a replacement for every allocator. It is a tool for a specific lifetime pattern.

#### A Practical Example: Building a List of Words

Suppose we want to copy several words into memory.

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit();

    const allocator = arena.allocator();

    const words = [_][]const u8{
        "red",
        "green",
        "blue",
    };

    var copied = std.ArrayList([]u8).init(allocator);

    for (words) |word| {
        const copy = try allocator.dupe(u8, word);
        try copied.append(copy);
    }

    for (copied.items) |word| {
        std.debug.print("{s}\n", .{word});
    }
}
```

Here, both the `ArrayList` storage and the copied strings come from the arena.

We do not free each string. We do not deinitialize the list separately in this beginner example because the arena will release its backing memory all at once.

The whole group has one lifetime:

```text
valid until arena.deinit()
```

#### When Not to Use an Arena

Do not use an arena when each object needs an independent lifetime.

For example, suppose you have a cache where entries are added and removed over time.

```text
insert item A
insert item B
remove item A
insert item C
remove item B
```

This is not a good arena pattern. A general purpose allocator is better because each item has its own lifetime.

Also avoid arenas when returned values must outlive the arena. Returning arena-owned memory from a short-lived function is a common beginner mistake.

#### The Core Idea

An arena allocator is useful when many allocations share one lifetime.

It changes cleanup from this:

```text
free item 1
free item 2
free item 3
free item 4
```

to this:

```text
free the arena
```

This is simple, fast, and easy to reason about when the lifetime is clear.

The rule is:

```text
Use an arena when the allocated data dies as a group.
```

