Iterator em Zig
O padrão Iterator fornece uma maneira de acessar os elementos de uma coleção sequencialmente sem expor sua representação interna. Em Zig, o protocolo de iteração é simples e convencionado: uma struct com um método next() que retorna ?T (optional do tipo do elemento). Quando retorna null, a iteração acabou.
Quando Usar
- Percorrer coleções customizadas
- Processamento lazy (sob demanda) de sequências
- Composição de transformações sem alocação intermediária
- Gerar sequências infinitas ou muito grandes
Protocolo de Iteração em Zig
const std = @import("std");
// Qualquer struct com next() -> ?T funciona com while
const Contador = struct {
atual: u32,
maximo: u32,
pub fn init(maximo: u32) Contador {
return .{ .atual = 0, .maximo = maximo };
}
pub fn next(self: *Contador) ?u32 {
if (self.atual >= self.maximo) return null;
const valor = self.atual;
self.atual += 1;
return valor;
}
};
pub fn main() void {
var contador = Contador.init(5);
// Protocolo while com capture
while (contador.next()) |valor| {
std.debug.print("{d} ", .{valor}); // 0 1 2 3 4
}
std.debug.print("\n", .{});
}
Iteradores da Biblioteca Padrão
const std = @import("std");
pub fn main() void {
// Split iterator
var split = std.mem.splitSequence(u8, "Zig,é,incrível", ",");
while (split.next()) |parte| {
std.debug.print("'{s}' ", .{parte});
}
std.debug.print("\n", .{});
// Tokenize (ignora delimitadores consecutivos)
var tokens = std.mem.tokenizeAny(u8, " Zig é incrível ", " ");
while (tokens.next()) |token| {
std.debug.print("'{s}' ", .{token});
}
std.debug.print("\n", .{});
// Window iterator
const dados = "ABCDEFGH";
var window = std.mem.window(u8, dados, 3, 1);
while (window.next()) |w| {
std.debug.print("{s} ", .{w}); // ABC BCD CDE DEF EFG FGH
}
std.debug.print("\n", .{});
}
Iterador Customizado com Estado
const std = @import("std");
const Fibonacci = struct {
a: u64 = 0,
b: u64 = 1,
limite: ?u64 = null,
pub fn init() Fibonacci {
return .{};
}
pub fn comLimite(limite: u64) Fibonacci {
return .{ .limite = limite };
}
pub fn next(self: *Fibonacci) ?u64 {
if (self.limite) |lim| {
if (self.a > lim) return null;
}
const valor = self.a;
const proximo = self.a +| self.b; // saturating add
self.a = self.b;
self.b = proximo;
return valor;
}
};
pub fn main() void {
var fib = Fibonacci.comLimite(100);
while (fib.next()) |n| {
std.debug.print("{d} ", .{n}); // 0 1 1 2 3 5 8 13 21 34 55 89
}
std.debug.print("\n", .{});
}
Composição de Iteradores
const std = @import("std");
fn MapIterator(comptime Src: type, comptime Dst: type) type {
return struct {
const Self = @This();
fonte: *Src,
transformar: *const fn (std.meta.Elem(Src)) Dst,
pub fn next(self: *Self) ?Dst {
const val = self.fonte.next() orelse return null;
return self.transformar(val);
}
};
}
fn FilterIterator(comptime Src: type) type {
return struct {
const Self = @This();
const T = std.meta.Elem(Src);
fonte: *Src,
predicado: *const fn (T) bool,
pub fn next(self: *Self) ?T {
while (self.fonte.next()) |val| {
if (self.predicado(val)) return val;
}
return null;
}
};
}
Iterador sobre Diretório
const std = @import("std");
pub fn main() !void {
var dir = try std.fs.cwd().openDir(".", .{ .iterate = true });
defer dir.close();
var iter = dir.iterate();
while (try iter.next()) |entrada| {
const tipo: []const u8 = switch (entrada.kind) {
.file => "ARQ",
.directory => "DIR",
else => "???",
};
std.debug.print("[{s}] {s}\n", .{ tipo, entrada.name });
}
}
Considerações de Performance
- Iteradores são lazy por natureza: o valor é calculado (ou lido) apenas quando
next()é chamado. Isso é excelente para sequências grandes — você processa um elemento de cada vez sem alocar um buffer intermediário com todos os elementos. - Zero alocação: iteradores customizados em Zig tipicamente vivem na stack. Não há overhead de heap, não há GC pause.
Fibonacci,Contador,FilterIterator— todos são structs de poucos bytes na stack. - Iteradores compostos com comptime:
MapIteratoreFilterIteratorparametrizados por comptime tipos permitem ao compilador inlinar a função de transformação/predicado. O resultado é equivalente a escrever o loop manualmente. - Atenção ao
hasNextvsnext: o protocolo Zig usa apenasnext() -> ?T. Não implementehasNextseparado — isso exige que o iterador “espreite” o próximo valor, o que pode ser caro para iteradores de I/O.
Iterador com Reset e Peek
Para casos onde você precisa olhar o próximo elemento sem avançar:
const PeekableContador = struct {
atual: u32,
maximo: u32,
_peeked: ?u32 = null,
pub fn peek(self: *PeekableContador) ?u32 {
if (self._peeked == null) {
self._peeked = self.next();
}
return self._peeked;
}
pub fn next(self: *PeekableContador) ?u32 {
if (self._peeked) |val| {
self._peeked = null;
return val;
}
if (self.atual >= self.maximo) return null;
const val = self.atual;
self.atual += 1;
return val;
}
};
O peek é útil em parsers onde você precisa decidir qual caminho seguir com base no próximo token sem consumi-lo.
Erros Comuns
Modificar a coleção durante a iteração: se você remove ou insere elementos em um ArrayList enquanto itera sobre ele com um iterador baseado em índice, o índice fica desincronizado. Prefira coletar os índices a remover e limpar após a iteração.
Iterador com referência à coleção que muda de endereço: se o iterador guarda um ponteiro para um ArrayList e o ArrayList é realocado (por append), o ponteiro fica dangling. Sempre passe o ArrayList como argumento para o iterador depois de todas as inserções, ou use ensureTotalCapacity antes de criar o iterador.
Retornar []T em vez de ?T em next: retornar um slice vazio como sentinela de fim (len == 0) é ambíguo — o iterador pode legitimamente retornar um slice vazio que representa um campo vazio em um CSV, por exemplo. Use sempre ?T onde null significa explicitamente “iteração encerrada”.
Perguntas Frequentes
Como criar um iterador que itera de trás para frente?
Implemente um ReverseIterator que guarda o índice atual iniciando em len e decrementa: if (self.idx == 0) return null; self.idx -= 1; return self.dados[self.idx];. O protocolo next() -> ?T funciona igualmente.
Posso usar iteradores Zig com for?
O for nativo de Zig (for (slice) |item|) funciona apenas com arrays, slices e ranges. Para iteradores customizados, use while (iter.next()) |val| { ... }. Não existe (ainda) sintaxe especial para iteradores customizados no for.
Como colecionar os resultados de um iterador em um ArrayList?
Use um loop com try lista.append(iter.next() orelse break), ou escreva uma função auxiliar: fn coletarIterador(iter: anytype, allocator: Allocator) !ArrayList(...).
Quando Evitar
- Arrays simples — use
fordireto:for (array) |item| { ... } - Quando todo o conteúdo precisa estar em memória de qualquer forma
- Operações que precisam de acesso aleatório (índice)
Veja Também
- Pipeline — Composição de estágios de processamento
- Arrays e Slices — Iteração sobre arrays nativos
- Strings — Iteradores de string e Unicode
- Operações de I/O — Readers como iteradores
- Biblioteca Padrão — Iteradores da std