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
| Function | Behaviour |
|---|---|
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.