Allocators

To allocate memory at runtime, use an Allocator:

const std = @import("std");
const expect = std.testing.expect;

test "use 'create' and 'destroy' for a single value" {
    // Unit tests usually use std.testing.allocator.
    const allocator = std.testing.allocator;
    const pointer = try allocator.create(u64);
    defer allocator.destroy(pointer);

    // Assign to allocated memory before using it.
    pointer.* = 4;
    try expect(pointer.* == 4);
}

test "use 'alloc' and 'free' for slices" {
    const allocator = std.testing.allocator;
    const slice = try allocator.alloc(u64, 4);
    defer allocator.free(slice);
    slice[3] = 3;
    try expect(slice[3] == 3);
}

test "use 'dupe' to copy a slice" {
    const allocator = std.testing.allocator;
    const original = [_]u64{ 0, 1, 2, 3 };
    const copy = try allocator.dupe(u64, &original);
    defer allocator.free(copy);
    try expect(copy[3] == 3);
}
$ zig test allocators.zig 
All 3 tests passed.

For non-test code, there’s several different options. The easiest is to define the main function with a std.process.Init argument, which provides two different allocators.

Init.gpa is a general-purpose allocator. This may be a different allocator depending on the platform and build mode; for debug builds it should also detect memory leaks.

const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;
    const pointer = try allocator.create(u64);
    defer allocator.destroy(pointer);
    pointer.* = 4;
    print("{}\n", .{pointer.*});
}
$ zig run allocators-2.zig 
4

We can also use Init.arena to get an arena allocator, which frees all memory on program exit so we don’t need to free individual allocations:

const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const allocator = init.arena.allocator();
    const pointer = try allocator.create(u64);
    pointer.* = 4;
    print("{}\n", .{pointer.*});
}
$ zig run allocators-3.zig 
4

For more on which allocator to use when, see Choosing an Allocator in the official documentation.

Running out of memory

If an allocation fails, we get an error value that we can handle like any other error.

const std = @import("std");
const expect = std.testing.expect;

// zeros returns a slice with all elements set to 0.
fn zeros(allocator: std.mem.Allocator) ![]u64 {
    const slice = try allocator.alloc(u64, 2);
    for (slice) |*p|
        p.* = 0;
    return slice;
}

test "zeros returns a slice with zeros" {
    const allocator = std.testing.allocator;
    const slice = try zeros(allocator);
    defer allocator.free(slice);
    for (slice) |v|
        try expect(v == 0);
}

test "zeros returns error on out-of-memory" {
    const ta = std.testing.allocator;

    // FailingAllocator can simulate out-of-memory.
    var fa = std.testing.FailingAllocator.init(ta, .{
        .fail_index = 1, // allow 1 allocation
    });
    const allocator = fa.allocator();

    const slice = try zeros(allocator);
    defer allocator.free(slice);

    try expect(zeros(allocator) == error.OutOfMemory);
}
$ zig test allocators-4.zig 
All 2 tests passed.

Next example: ArrayList.