⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

ArrayList

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

test "build up a list" {
    const gpa = std.testing.allocator;

    // `.empty` replaces `init(allocator)`. The list does not store the
    // allocator, so every method that may allocate takes one.
    var list: std.ArrayList(u8) = .empty;
    defer list.deinit(gpa);

    try list.append(gpa, 'h');
    try list.appendSlice(gpa, "ello");

    try expect(std.mem.eql(u8, list.items, "hello"));
    try expect(list.items.len == 5);
}

test "items is a plain slice" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(u32) = .empty;
    defer list.deinit(gpa);

    for (0..5) |i| try list.append(gpa, @intCast(i * i));

    // `.items` is a `[]T` into the list's buffer, valid until the next
    // reallocation, so do not hold it across an append.
    try expect(list.items[4] == 16);
}

test "pop and remove" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(u8) = .empty;
    defer list.deinit(gpa);

    try list.appendSlice(gpa, "abcd");
    try expect(list.pop().? == 'd');
    _ = list.orderedRemove(0); // shifts the rest down
    try expect(std.mem.eql(u8, list.items, "bc"));
}

test "preallocate when the size is known" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(u8) = .empty;
    defer list.deinit(gpa);

    // One allocation instead of a growth sequence.
    try list.ensureTotalCapacity(gpa, 100);
    for (0..100) |_| list.appendAssumeCapacity('x');
    try expect(list.items.len == 100);
}

test "take ownership of the buffer" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(u8) = .empty;
    try list.appendSlice(gpa, "owned");

    // `toOwnedSlice` hands you the memory and empties the list, so the
    // caller frees the slice rather than deinit-ing the list.
    const slice = try list.toOwnedSlice(gpa);
    defer gpa.free(slice);
    try expect(std.mem.eql(u8, slice, "owned"));
}

It is unmanaged now

This is the change most likely to break code you find online. std.ArrayList used to store its allocator:

var list = std.ArrayList(u8).init(allocator);   // old
defer list.deinit();
try list.append('a');

On master, ArrayList is the unmanaged variant. It holds no allocator, so you supply one to every method that might allocate:

var list: std.ArrayList(u8) = .empty;           // current
defer list.deinit(gpa);
try list.append(gpa, 'a');

More typing, but the allocator is visible at every call site that can fail, and the struct is two words smaller.

.items is a borrowed slice

list.items points into the list’s buffer. Appending may reallocate, which invalidates any slice you were holding. Take .items when you need it; do not stash it across mutations.

Preallocate when you can

ensureTotalCapacity followed by appendAssumeCapacity turns a growth sequence into one allocation, and the AssumeCapacity calls cannot fail, so they need no try.

Handing off ownership

toOwnedSlice(gpa) gives you the buffer and leaves the list empty. The caller now frees the slice, and the list needs no deinit.