Reader Interface em Zig — O que é e Como Usar
Definição
A Reader interface em Zig é o padrão utilizado pela biblioteca padrão para abstração de leitura de dados. Complementar à Writer interface, qualquer tipo que forneça uma função read com a assinatura correta pode ser usado como Reader. Isso permite escrever código genérico que lê de stdin, arquivos, buffers em memória, sockets de rede ou qualquer outra fonte de dados.
Por que Reader Interface Importa
- Código genérico: Funções que aceitam reader funcionam com qualquer fonte de dados.
- Testabilidade: Ler de buffer em testes, de arquivo em produção.
- Composição: Readers podem ser encadeados (buffered, limited, etc.).
- Processamento de streams: Ler dados incrementalmente sem carregar tudo na memória.
Exemplo Prático
Leitura Linha por Linha
const std = @import("std");
fn contarLinhas(reader: anytype) !usize {
var linhas: usize = 0;
while (true) {
_ = reader.readUntilDelimiterOrEof('\n') catch |err| switch (err) {
error.StreamTooLong => continue,
else => return err,
} orelse break;
linhas += 1;
}
return linhas;
}
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
defer arquivo.close();
var buf_reader = std.io.bufferedReader(arquivo.reader());
const reader = buf_reader.reader();
var buffer: [4096]u8 = undefined;
var linhas: usize = 0;
while (true) {
const linha = reader.readUntilDelimiterOrEof(&buffer, '\n') catch continue;
if (linha == null) break;
linhas += 1;
}
std.debug.print("Total de linhas: {}\n", .{linhas});
}
Ler Bytes com Reader Genérico
const std = @import("std");
fn lerExatamente(reader: anytype, n: usize, buffer: []u8) ![]u8 {
if (n > buffer.len) return error.BufferPequeno;
const lidos = try reader.readAll(buffer[0..n]);
if (lidos < n) return error.FimPrematuro;
return buffer[0..n];
}
test "ler de buffer em memória" {
const dados = "Hello, Zig Brasil!";
var stream = std.io.fixedBufferStream(dados);
const reader = stream.reader();
var buffer: [5]u8 = undefined;
const resultado = try lerExatamente(reader, 5, &buffer);
try std.testing.expectEqualStrings("Hello", resultado);
}
Buffered Reader para Performance
const std = @import("std");
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("grande.bin", .{});
defer arquivo.close();
// Buffered reader reduz syscalls de leitura
var buf_reader = std.io.bufferedReader(arquivo.reader());
const reader = buf_reader.reader();
var total: u64 = 0;
var buffer: [4096]u8 = undefined;
while (true) {
const n = try reader.read(&buffer);
if (n == 0) break; // EOF
total += n;
}
std.debug.print("Total lido: {} bytes\n", .{total});
}
Readers Comuns na std lib
| Reader | Descrição |
|---|---|
std.io.getStdIn().reader() | Entrada padrão (stdin) |
file.reader() | Leitura de arquivo |
stream.reader() | Leitura de fixedBufferStream |
std.io.bufferedReader(r) | Reader com buffer |
std.io.limitedReader(r, n) | Reader que limita bytes lidos |
Métodos do Reader
| Método | Descrição |
|---|---|
read(buffer) | Ler até buffer.len bytes |
readAll(buffer) | Ler exatamente buffer.len bytes |
readByte() | Ler um único byte |
readUntilDelimiterOrEof(buf, delim) | Ler até delimitador ou EOF |
skipBytes(n) | Pular n bytes |
readInt(T) | Ler inteiro com endianness |
Armadilhas Comuns
- read retorna 0 no EOF: Sempre verifique se
read()retornou 0 — isso indica fim dos dados. - read pode retornar menos bytes: Uma chamada a
read()pode retornar menos bytes do que o buffer. UsereadAll()quando precisar de quantidade exata. - Buffer insuficiente:
readUntilDelimiterOrEofretorna erro se a linha exceder o buffer. - Performance sem buffer: Sem
bufferedReader, cadareadByte()gera uma syscall.
Implementando seu Próprio Reader
Qualquer struct que implemente o método read com a assinatura correta pode ser usada como Reader. Isso é feito via std.io.GenericReader:
const std = @import("std");
// Reader que repete um byte infinitamente
const RepeatReader = struct {
byte: u8,
pub const Reader = std.io.Reader(*RepeatReader, error{}, lerBytes);
fn lerBytes(self: *RepeatReader, buffer: []u8) error{}!usize {
for (buffer) |*b| b.* = self.byte;
return buffer.len;
}
pub fn reader(self: *RepeatReader) Reader {
return .{ .context = self };
}
};
test "repeat reader" {
var rep = RepeatReader{ .byte = 'A' };
var buf: [5]u8 = undefined;
_ = try rep.reader().readAll(&buf);
try std.testing.expectEqualStrings("AAAAA", &buf);
}
Boas Práticas com Reader
Sempre use bufferedReader ao ler de arquivo ou socket: Sem buffer, cada chamada a readByte() ou readUntilDelimiterOrEof gera uma syscall separada. O buffered reader agrupa as syscalls em blocos de 4096 bytes por padrão.
Prefira readAll a read quando precisar de quantidade exata: read() pode retornar menos bytes que o solicitado — isso não é erro, é comportamento normal de I/O. readAll() garante que o buffer seja preenchido completamente ou retorna error.EndOfStream.
Use anytype para funções genéricas de leitura: Isso permite que a função aceite qualquer tipo de reader sem overhead de vtable:
fn parsearCSV(reader: anytype, allocator: std.mem.Allocator) ![][]u8 {
var linhas = std.ArrayList([]u8).init(allocator);
var buf: [4096]u8 = undefined;
while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |linha| {
try linhas.append(try allocator.dupe(u8, linha));
}
return linhas.toOwnedSlice();
}
Termos Relacionados
- Writer Interface — Interface de escrita complementar
- anytype — Mecanismo de genéricos usado pela interface
- Slice — Buffers são slices de bytes
- Error Union — Readers retornam erros de I/O