Iterator Pattern em Zig — O que é e Como Usar
Definição
O Iterator Pattern (padrão de iteração) em Zig segue a convenção de expor um método next() que retorna ?T — ou seja, um optional que é null quando a iteração termina. Diferente de linguagens com traits/interfaces formais de iterador (como Rust ou Java), Zig usa duck typing via anytype ou convenção explícita. Qualquer struct com um método next() que retorne optional pode ser usada como iterador.
Este padrão é usado extensivamente na biblioteca padrão para tokenização de strings, iteração sobre diretórios, leitura de linhas e muito mais.
Por que Iterator Pattern Importa
- Avaliação preguiçosa: Dados são produzidos sob demanda, sem alocar tudo na memória.
- Composição: Iteradores podem ser encadeados (map, filter, take).
- Convenção simples: Apenas
next() -> ?T— sem interface formal complexa. - Uso com while: Integra naturalmente com
while (iter.next()) |item|.
Exemplo Prático
Iterador Simples
const std = @import("std");
const Intervalo = struct {
atual: usize,
fim: usize,
pub fn init(inicio: usize, fim: usize) Intervalo {
return .{ .atual = inicio, .fim = fim };
}
pub fn next(self: *Intervalo) ?usize {
if (self.atual >= self.fim) return null;
const valor = self.atual;
self.atual += 1;
return valor;
}
};
pub fn main() void {
var iter = Intervalo.init(5, 10);
while (iter.next()) |valor| {
std.debug.print("{} ", .{valor}); // 5 6 7 8 9
}
std.debug.print("\n", .{});
}
Tokenizador de Strings (da std lib)
const std = @import("std");
pub fn main() void {
const csv = "nome,idade,cidade,país";
// std.mem.splitScalar retorna um iterador
var iter = std.mem.splitScalar(u8, csv, ',');
while (iter.next()) |campo| {
std.debug.print("Campo: '{s}'\n", .{campo});
}
// Campo: 'nome'
// Campo: 'idade'
// Campo: 'cidade'
// Campo: 'país'
}
Iterador com Estado Complexo
const std = @import("std");
fn Fibonacci() type {
return struct {
a: u64 = 0,
b: u64 = 1,
contagem: usize = 0,
limite: usize,
const Self = @This();
pub fn init(limite: usize) Self {
return .{ .limite = limite };
}
pub fn next(self: *Self) ?u64 {
if (self.contagem >= self.limite) return null;
self.contagem += 1;
const resultado = self.a;
const novo = self.a +| self.b; // saturating add
self.a = self.b;
self.b = novo;
return resultado;
}
};
}
pub fn main() void {
var fib = Fibonacci().init(10);
while (fib.next()) |valor| {
std.debug.print("{} ", .{valor});
}
// 0 1 1 2 3 5 8 13 21 34
std.debug.print("\n", .{});
}
Convenção do Iterador
// Qualquer struct com este método é um iterador:
pub fn next(self: *Self) ?ItemType {
// Retornar próximo item ou null se acabou
}
// Uso idiomático:
while (iter.next()) |item| {
// processar item
}
Iteradores na std lib
| Iterador | Uso |
|---|---|
std.mem.splitScalar | Dividir string por caractere |
std.mem.splitSequence | Dividir por sequência |
std.mem.tokenizeScalar | Tokenizar ignorando delimitadores consecutivos |
std.mem.window | Janela deslizante sobre slice |
std.fs.Dir.iterate() | Iterar entradas de diretório |
Armadilhas Comuns
- Mutabilidade: O iterador precisa ser
var, nãoconst, poisnext()modifica estado interno. - Consumo único: A maioria dos iteradores só pode ser percorrida uma vez. Para percorrer novamente, crie outro iterador.
- Sem reset: Não há convenção de “reset” — crie uma nova instância.
- null vs fim:
nullsignifica “sem mais itens”, não erro. Para iteradores que podem falhar, o retorno deve ser!?T.
Termos Relacionados
- Optional — Tipo de retorno
?Tdonext() - Struct — Iteradores são structs com estado
- anytype — Funções genéricas aceitam qualquer iterador
- Slice — Fonte comum de dados para iteração