Stacks
const std = @import("std");
const expect = std.testing.expect;
test "ArrayList is the stack you want" {
const gpa = std.testing.allocator;
var stack: std.ArrayList(u32) = .empty;
defer stack.deinit(gpa);
try stack.append(gpa, 1);
try stack.append(gpa, 2);
try stack.append(gpa, 3);
try expect(stack.pop().? == 3);
try expect(stack.getLast() == 2);
try expect(stack.items.len == 2);
}
test "pop returns null when empty" {
const gpa = std.testing.allocator;
var stack: std.ArrayList(u8) = .empty;
defer stack.deinit(gpa);
try expect(stack.pop() == null);
}
test "a bounded stack needs no allocator at all" {
// For a known maximum, a fixed buffer avoids the heap entirely.
var buffer: [8]u32 = undefined;
var len: usize = 0;
for ([_]u32{ 10, 20, 30 }) |v| {
buffer[len] = v;
len += 1;
}
len -= 1;
try expect(buffer[len] == 30);
}
test "balanced bracket check" {
const gpa = std.testing.allocator;
var stack: std.ArrayList(u8) = .empty;
defer stack.deinit(gpa);
const input = "([]{})";
var balanced = true;
for (input) |c| switch (c) {
'(', '[', '{' => try stack.append(gpa, c),
')' => balanced = balanced and stack.pop() == '(',
']' => balanced = balanced and stack.pop() == '[',
'}' => balanced = balanced and stack.pop() == '{',
else => {},
};
try expect(balanced and stack.items.len == 0);
}Zig has no dedicated Stack type because it does not need one. An ArrayList
used from the end is a stack:
| Operation | Call |
|---|---|
| push | append(gpa, x) |
| pop | pop() (returns ?T) |
| peek | getLast() |
| size | items.len |
pop() returns an optional, so the empty case is handled by the type rather
than by a precondition you might forget.
When the depth is bounded
If you know the maximum, skip the allocator entirely: a fixed array plus a length index is a complete stack with no failure mode. This matters for embedded targets and hot paths, and it is the sort of thing Zig makes comfortable rather than exotic.
Why not a linked list
A linked-list stack allocates per element and scatters them across memory.
ArrayList amortises to one allocation and keeps everything contiguous, which
is faster for essentially every real workload. Reach for a linked list only
when you need stable addresses or O(1) splicing.