Skip to content

Why Allocation Is Explicit

A program uses memory to store values.

A program uses memory to store values.

Some values live for the entire execution of the program. Others exist only while a function runs. Some must grow while the program is running. Some must be shared between different parts of the program.

Zig does not hide memory allocation behind the language. Allocation is explicit.

This is one of the central design choices of Zig.

In some languages, memory is managed automatically by a garbage collector. In others, allocation is hidden inside containers or runtime objects. Zig takes a different approach. The allocator is passed directly to the code that needs memory.

A Zig program therefore answers three questions clearly:

  1. Where does the memory come from?
  2. Who owns it?
  3. When is it released?

Consider a fixed-size array:

const std = @import("std");

pub fn main() void {
    var values = [_]i32{ 1, 2, 3, 4, 5 };

    std.debug.print("{d}\n", .{values[0]});
}

The array lives on the stack. Its size is known at compile time. No allocator is needed.

But many programs do not know sizes in advance.

A text editor does not know how large a file will be. A web server does not know how many requests will arrive. A parser does not know how many tokens it will read.

In these cases memory must be requested while the program is running.

A simple allocation looks like this:

const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const values = try allocator.alloc(i32, 5);
    defer allocator.free(values);

    for (values, 0..) |*value, i| {
        value.* = @intCast(i);
    }

    std.debug.print("{any}\n", .{values});
}

Several important things happen here.

First, the program chooses an allocator:

const allocator = std.heap.page_allocator;

The allocator defines where memory comes from and how it is managed.

Next, memory is allocated:

const values = try allocator.alloc(i32, 5);

This requests space for five i32 values.

Allocation can fail. The operating system may refuse the request. Zig therefore returns an error union, and try propagates the error if allocation fails.

The allocated memory must later be released:

defer allocator.free(values);

defer guarantees that free runs when the current scope exits.

The language does not insert hidden cleanup code. The programmer decides when memory is released.

This style produces programs with visible ownership rules.

The function that allocates memory is usually responsible for freeing it, unless ownership is transferred explicitly.

For example:

fn buildMessage(allocator: std.mem.Allocator) ![]u8

This function signature immediately tells the reader several things:

  • the function may allocate memory
  • allocation may fail
  • the caller provides the allocator
  • the caller probably owns the returned memory

The allocation policy is therefore not hidden inside the function.

Different allocators serve different purposes.

A page allocator requests memory directly from the operating system. An arena allocator allocates many objects together and frees them all at once. A fixed-buffer allocator uses a pre-existing block of memory without requesting more from the system.

Because allocators are explicit, the same program logic can often work with different allocation strategies.

This is a common Zig pattern:

fn process(allocator: std.mem.Allocator) !void

The function does not care where memory comes from. It only uses the allocator interface.

Explicit allocation also improves predictability.

There is no hidden garbage collector running in the background. Memory usage, allocation points, and cleanup behavior are visible in the source code.

This matters in systems programming, game engines, embedded software, networking code, and low-latency applications, where uncontrolled allocation can become a performance problem.

Explicit allocation does require more discipline. Memory leaks, double frees, and dangling pointers are still possible if ownership rules are unclear.

Zig therefore encourages simple allocation structures:

  • allocate in one place
  • free in one place
  • keep ownership obvious
  • avoid hidden sharing
  • prefer stack allocation when possible

Much of the Zig standard library follows these rules closely.

In the chapters that follow, we will examine the allocator interface itself, the standard allocators provided by Zig, and common patterns for managing memory safely.