🎓 A1Learner
🔍
0 of 55 lessons completed

Zig Dynamic Arrays

A dynamic array is a slice whose items live in memory reserved at run time, so its length can come from a value the program computes. The length is settled once the memory is reserved.

The length can come from a variable

size grows to 4 before the alloc call reads it. The reserved slice reports a length of 4.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;
    var size: usize = 2;
    size += 2;
    const buf = try alloc.alloc(i32, size);
    defer alloc.free(buf);
    for (0..buf.len) |i| {
        buf[i] = @intCast(i + 1);
    }
    try out.print("len = {d}\n", .{buf.len});
    try out.print("{any}\n", .{buf});
}

A loop builds the contents in place

Five items are reserved and filled with multiples of 7. The print shows all five computed values.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;
    const items = try alloc.alloc(i32, 5);
    defer alloc.free(items);
    for (0..items.len) |i| {
        items[i] = @intCast((i + 1) * 7);
    }
    try out.print("{any}\n", .{items});
}

One program can reserve two sizes

The small slice is freed before the bigger one is reserved. Each print shows the size that was actually asked for.

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;
    const small = try alloc.alloc(i32, 2);
    small[0] = 1;
    small[1] = 2;
    try out.print("{any}\n", .{small});
    alloc.free(small);
    const big = try alloc.alloc(i32, 4);
    defer alloc.free(big);
    for (0..big.len) |i| {
        big[i] = @intCast(i + 1);
    }
    try out.print("{any}\n", .{big});
}

Example

const std = @import("std");

pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;
    var count: usize = 3;
    count += 2;
    const squares = try alloc.alloc(i32, count);
    defer alloc.free(squares);
    for (0..squares.len) |i| {
        const n: i32 = @intCast(i + 1);
        squares[i] = n * n;
    }
    var total: i32 = 0;
    for (squares) |s| {
        total += s;
    }
    try out.print("{any}\n", .{squares});
    try out.print("total = {d}\n", .{total});
}

Key points

  • Dynamic array: a slice backed by allocator memory instead of a fixed-size array.
  • Run-time length: the count passed to alloc can be a variable, not only a literal.
  • Same indexing: items[i] reads and writes items just like an array.
  • Same length field: items.len reports how many items were reserved.
  • Freeing: the slice is released with alloc.free when the program is done with it.

In this example, count ends at 5, so alloc reserves a dynamic array of five i32 values. The first loop stores the square of each position, giving 1, 4, 9, 16, and 25. The second loop adds them, and the total print shows 55.

Note

a dynamic array does not grow on its own, so ask alloc for the full size up front.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When is a dynamic array a better fit than a fixed array like [4]i32?