Using Array List in Zig

In this example program, we see a practical demonstration of Zig’s memory management, standard library usage, and its ArrayList data structure. The program initializes a dynamic list of integers, performs common operations like appending, modifying, iterating, and popping elements, and outputs the results to standard output—all while properly handling memory allocation and deallocation.

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;
    var numbers = std.ArrayList(i32).empty;
    defer numbers.deinit(allocator);

    const stdout = std.Io.File.stdout();
    try stdout.writeStreamingAll(init.io, "Adding numbers 0, 10, 20, 30, 40...\n");

    var i: i32 = 0;
    while (i < 5) : (i += 1) {
        try numbers.append(allocator, i * 10);
    }

    if (numbers.items.len > 2) {
        numbers.items[2] = 999;
        try stdout.writeStreamingAll(init.io, "Modified index 2 to 999.\n");
    }

    try stdout.writeStreamingAll(init.io, "List contents: ");
    for (numbers.items) |num| {
        const num_str = try std.fmt.allocPrint(allocator, "{d} ", .{num});
        defer allocator.free(num_str);
        try stdout.writeStreamingAll(init.io, num_str);
    }
    try stdout.writeStreamingAll(init.io, "\n");

    const last = numbers.pop();
    const pop_msg = try std.fmt.allocPrint(allocator, "Popped last item: {?d}\n", .{last});
    defer allocator.free(pop_msg);
    try stdout.writeStreamingAll(init.io, pop_msg);

    const len_msg = try std.fmt.allocPrint(allocator, "Final count: {d}\n", .{numbers.items.len});
    defer allocator.free(len_msg);
    try stdout.writeStreamingAll(init.io, len_msg);
}

The program begins by importing the standard library. It then defines the main() function using Zig’s newer initialization pattern that receives a process.Init parameter. This provides easy access to a General Purpose Allocator and I/O context. An unmanaged ArrayList of 32-bit integers is created. A defer statement ensures the list’s memory is automatically cleaned up when the function ends — a great example of Zig’s reliable resource management.

The program first announces its actions to the console and then populates the list by appending the numbers 0, 10, 20, 30, and 40 in a simple while loop. After filling the list, it modifies the element at index 2, changing the original value of 20 to 999. A length check safely guards against out-of-bounds access. Next, the program iterates over the list contents. For each number, it formats a string representation, prints it, and immediately defers the deallocation of that temporary string. This allocate-use-defer-free pattern is very idiomatic in Zig. Finally, the program pops the last element from the list — which returns an optional value for safe handling of empty lists — prints the popped item, and reports the final count of remaining elements. All output is handled through streaming I/O.

Save the code as arrayList.zig and try it:

$ zig version
0.16.0
$ zig run arrayList.zig
Adding numbers 0, 10, 20, 30, 40...
Modified index 2 to 999.
List contents: 0 10 999 30 40
Popped last item: 40
Final count: 4

Happy coding in Zig!