Lesson 12 / 25
ArrayList, HashMap and Other Containers
Use std containers with explicit allocators.
Growable containers
The standard library provides containers that take allocators. std.ArrayList(T) is a growable array; since Zig 0.15 it is unmanaged by default: it does not store an allocator, so you pass one to methods that may allocate (try list.append(gpa, item), list.deinit(gpa)), and you create it empty with .empty. Its items field is a slice of the current elements; appending can reallocate, so slices and pointers into items may become invalid. toOwnedSlice(gpa) transfers ownership of the buffer to the caller. Hash maps come in several flavours: std.AutoHashMap(K, V) hashes most key types automatically, std.StringHashMap(V) uses string contents as keys, and ArrayHashMap variants preserve insertion order; getOrPut inserts or updates in one lookup. Keys and values are stored by value, so string keys are not copied: the bytes must outlive the map, or you must dupe them. Other containers include std.MultiArrayList (struct-of-arrays layout for cache efficiency), std.PriorityQueue, std.BufSet and std.SinglyLinkedList. Because the standard library is evolving, check the API for your Zig version, especially whether a container is managed or unmanaged.
Revenue by city with ArrayList and StringHashMap
Unmanaged ArrayList (Zig 0.15 style) and getOrPut.
const std = @import("std");
const Order = struct { city: []const u8, total_paise: u64 };
pub fn main() !void {
var debug_alloc: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_alloc.deinit();
const gpa = debug_alloc.allocator();
// ArrayList is unmanaged in Zig 0.15: pass the allocator to methods
var orders: std.ArrayList(Order) = .empty;
defer orders.deinit(gpa);
try orders.append(gpa, .{ .city = "Pune", .total_paise = 120_000 });
try orders.append(gpa, .{ .city = "Delhi", .total_paise = 30_000 });
try orders.append(gpa, .{ .city = "Pune", .total_paise = 80_000 });
// StringHashMap stores the allocator; keys must outlive the map (literals do)
var revenue = std.StringHashMap(u64).init(gpa);
defer revenue.deinit();
for (orders.items) |o| {
const entry = try revenue.getOrPut(o.city);
if (!entry.found_existing) entry.value_ptr.* = 0;
entry.value_ptr.* += o.total_paise;
}
var it = revenue.iterator();
while (it.next()) |kv| {
std.debug.print("{s}: {d}\n", .{ kv.key_ptr.*, kv.value_ptr.* });
}
const last = orders.pop(); // ?Order: null when empty
if (last) |o| std.debug.print("removed last order from {s}\n", .{o.city});
}Beware of invalidated slices
Holding const first = &list.items[0]; and then appending may move the buffer, leaving first dangling. Store indices instead of pointers when a container can grow.
Quick check: In Zig 0.15, how do you append to an unmanaged std.ArrayList?
- try list.append(allocator, item)
- list.append(item)
- list.push(item)
- ArrayLists cannot grow
Answer
try list.append(allocator, item) — Unmanaged containers take the allocator as an argument to allocating methods.