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

Iterators

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

// Zig has no Iterator trait. A type is iterable if it has `next()` returning
// an optional, because that is what `while (it.next()) |x|` needs.
const Countdown = struct {
    remaining: u32,

    pub fn next(self: *Countdown) ?u32 {
        if (self.remaining == 0) return null;
        self.remaining -= 1;
        return self.remaining;
    }
};

test "write your own iterator" {
    var it = Countdown{ .remaining = 3 };
    var sum: u32 = 0;
    while (it.next()) |value| sum += value;
    try expect(sum == 3); // 2 + 1 + 0
}

test "split on a single byte" {
    var it = std.mem.splitScalar(u8, "a,b,c", ',');
    try expect(std.mem.eql(u8, it.next().?, "a"));
    try expect(std.mem.eql(u8, it.next().?, "b"));
    try expect(std.mem.eql(u8, it.next().?, "c"));
    try expect(it.next() == null);
}

test "split keeps empty fields, tokenize does not" {
    var split = std.mem.splitScalar(u8, "a,,b", ',');
    var split_count: u32 = 0;
    while (split.next()) |_| split_count += 1;
    try expect(split_count == 3); // "a", "", "b"

    var tokens = std.mem.tokenizeScalar(u8, "a,,b", ',');
    var token_count: u32 = 0;
    while (tokens.next()) |_| token_count += 1;
    try expect(token_count == 2); // "a", "b"
}

test "tokenize on any of several separators" {
    var it = std.mem.tokenizeAny(u8, "one two\tthree", " \t");
    try expect(std.mem.eql(u8, it.next().?, "one"));
    try expect(std.mem.eql(u8, it.next().?, "two"));
    try expect(std.mem.eql(u8, it.next().?, "three"));
}

test "window and chunk a slice" {
    var windows = std.mem.window(u8, "abcd", 2, 1); // overlapping
    try expect(std.mem.eql(u8, windows.next().?, "ab"));
    try expect(std.mem.eql(u8, windows.next().?, "bc"));
}

Zig has no Iterator trait, no IntoIterator, no iterator protocol to implement. A type is iterable if it has a next() method returning an optional, because that is exactly what this loop needs:

while (it.next()) |value| { ... }

Writing your own is a struct with state and a next. That is the whole mechanism.

Splitting text

FunctionBehaviour
splitScalar(u8, s, ',')split on one byte, keeps empty fields
splitAny(u8, s, ",;")split on any of several bytes
splitSequence(u8, s, "::")split on a multi-byte separator
tokenizeScalar(u8, s, ',')same, but skips empty fields
tokenizeAny(u8, s, " \t")skip empties, any of several separators

The split/tokenize distinction is the one to remember: parsing CSV wants split (an empty field is meaningful); splitting on whitespace wants tokenize (runs of spaces should not produce empty tokens).

Windows and chunks

std.mem.window(T, s, size, advance) yields overlapping views; std.mem.chunk yields non-overlapping ones. Both are useful for n-gram and block processing without copying.

No lazy adapter chains

There is no .map().filter().take(). Write the loop: it is usually shorter than the chain, and there is nothing hidden about its cost.