Skip to content

General-Purpose Allocator

The general-purpose allocator is used for ordinary heap allocation.

The general-purpose allocator is used for ordinary heap allocation.

It is designed for programs that allocate and free many objects of different sizes.

A typical program sets it up near main:

const std = @import("std");

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

    const allocator = gpa.allocator();

    const bytes = try allocator.alloc(u8, 128);
    defer allocator.free(bytes);

    @memset(bytes, 0);

    std.debug.print("allocated {d} bytes\n", .{bytes.len});
}

The value gpa is the allocator state. The value allocator is the allocator interface used by the rest of the program.

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

The state must live as long as any memory allocated from it. Do not return an allocator whose backing state has gone out of scope.

This is wrong:

fn makeAllocator() std.mem.Allocator {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    return gpa.allocator(); // wrong
}

The returned allocator refers to gpa, but gpa is destroyed when the function returns.

The allocator value is small, but it is not the whole allocator. It points to allocator state.

The general-purpose allocator must be deinitialized:

defer _ = gpa.deinit();

The call releases internal state and may report leaks, depending on configuration and build mode. In examples, the return value is often ignored with _ =.

For reusable code, pass the allocator down:

fn work(allocator: std.mem.Allocator) !void {
    const names = try allocator.alloc([]const u8, 4);
    defer allocator.free(names);

    names[0] = "zig";
    names[1] = "c";
    names[2] = "go";
    names[3] = "rust";
}

The function does not know whether the caller used a general-purpose allocator, an arena, a fixed buffer, or a testing allocator.

That is the point.

The general-purpose allocator is a good default for applications. It is less suitable for short-lived batches of objects that all die at the same time. For that, an arena is often simpler.

Compare these two cases.

First, mixed lifetimes:

const user = try allocator.create(User);
defer allocator.destroy(user);

const buffer = try allocator.alloc(u8, 4096);
defer allocator.free(buffer);

The objects may be created and destroyed at different times. A general-purpose allocator fits this pattern.

Second, one shared lifetime:

const tokens = try allocator.alloc(Token, token_count);
const nodes = try allocator.alloc(Node, node_count);
const names = try allocator.alloc([]const u8, name_count);

If all of these are temporary parser data and will be freed together, an arena may fit better.

The general-purpose allocator gives flexibility. It also requires exact cleanup. Each allocation must be paired with the correct release operation.

Use:

allocator.free(slice);

for memory returned by alloc.

Use:

allocator.destroy(pointer);

for memory returned by create.

A common application shape is:

const std = @import("std");

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

    try run(gpa.allocator());
}

fn run(allocator: std.mem.Allocator) !void {
    var list = std.ArrayList(u8).init(allocator);
    defer list.deinit();

    try list.appendSlice("hello");
    try list.append(' ');
    try list.appendSlice("zig");

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

main chooses the allocator. run receives it. The rest of the program uses it explicitly.

This pattern keeps allocation policy at the edge of the program and allocation use inside the program.

Exercises:

Exercise 12-9. Create a general-purpose allocator in main, allocate 256 bytes, fill them with 1, and free them.

Exercise 12-10. Write a run function that receives std.mem.Allocator and builds an ArrayList(u32).

Exercise 12-11. Write a program that allocates one Point struct with create, sets its fields, prints them, and destroys it.

Exercise 12-12. Explain why returning gpa.allocator() from a function with a local gpa is invalid.