Reader Interface em Zig — O que é e Como Usar

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

  1. Código genérico: Funções que aceitam reader funcionam com qualquer fonte de dados.
  2. Testabilidade: Ler de buffer em testes, de arquivo em produção.
  3. Composição: Readers podem ser encadeados (buffered, limited, etc.).
  4. 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

ReaderDescriçã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étodoDescriçã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. Use readAll() quando precisar de quantidade exata.
  • Buffer insuficiente: readUntilDelimiterOrEof retorna erro se a linha exceder o buffer.
  • Performance sem buffer: Sem bufferedReader, cada readByte() 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

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.