std.io.Reader — Interface de Leitura
A interface Reader é o mecanismo genérico do Zig para leitura de bytes. Assim como o Writer, ela é parametrizada em tempo de compilação, permitindo que o mesmo código funcione com arquivos, sockets, buffers em memória e qualquer outro tipo que forneça uma função de leitura.
Visão Geral
O Reader é definido pelo tipo parametrizado GenericReader:
pub fn GenericReader(
comptime Context: type,
comptime ReadError: type,
comptime readFn: fn (Context, []u8) ReadError!usize,
) type
A função readFn deve:
- Preencher o buffer fornecido com dados lidos
- Retornar o número de bytes efetivamente lidos
- Retornar 0 para indicar fim do fluxo (EOF)
Métodos Principais
Leitura Básica
// Lê bytes para o buffer; retorna quantos foram lidos (0 = EOF)
pub fn read(self: Self, buffer: []u8) ReadError!usize
// Lê exatamente buffer.len bytes ou retorna erro
pub fn readAll(self: Self, buffer: []u8) ReadError!usize
// Lê exatamente N bytes; retorna erro se não conseguir
pub fn readNoEof(self: Self, buf: *[N]u8) (ReadError || error{EndOfStream})!void
// Lê um único byte
pub fn readByte(self: Self) (ReadError || error{EndOfStream})!u8
Leitura por Delimitador
// Lê até encontrar o delimitador ou EOF
pub fn readUntilDelimiterOrEof(
self: Self,
buf: []u8,
delimiter: u8,
) (ReadError || error{StreamTooLong})!?[]u8
// Lê até o delimitador, alocando memória conforme necessário
pub fn readUntilDelimiterAlloc(
self: Self,
allocator: std.mem.Allocator,
delimiter: u8,
max_size: usize,
) (ReadError || error{StreamTooLong} || Allocator.Error)![]u8
Leitura de Tipos
// Lê um inteiro com endianness especificado
pub fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) !T
// Lê uma struct empacotada (packed)
pub fn readStruct(self: Self, comptime T: type) !T
Controle de Fluxo
// Descarta N bytes do fluxo
pub fn skipBytes(self: Self, num_bytes: u64, options: SkipBytesOptions) !void
// Lê e descarta todos os bytes restantes
pub fn discard(self: Self) !u64
// Verifica se o reader ainda tem dados
pub fn any(self: Self) bool
Exemplo 1: Leitura Linha por Linha
O caso de uso mais comum é ler um fluxo linha por linha:
const std = @import("std");
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 buf: [4096]u8 = undefined;
var num_linha: usize = 0;
while (reader.readUntilDelimiterOrEof(&buf, '\n')) |maybe_linha| {
if (maybe_linha) |linha| {
num_linha += 1;
std.debug.print("{d:>4}: {s}\n", .{ num_linha, linha });
} else {
break; // EOF
}
} else |err| {
std.debug.print("Erro na leitura: {}\n", .{err});
}
std.debug.print("\nTotal: {d} linhas\n", .{num_linha});
}
Exemplo 2: Leitura da Entrada Padrão com Alocação
Usando readUntilDelimiterAlloc para linhas de tamanho variável:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const stdin = std.io.getStdIn().reader();
const stdout = std.io.getStdOut().writer();
try stdout.writeAll("Digite palavras (uma por linha, 'sair' para encerrar):\n");
var palavras = std.ArrayList([]const u8).init(allocator);
defer {
for (palavras.items) |p| allocator.free(p);
palavras.deinit();
}
while (true) {
try stdout.writeAll("> ");
const linha = stdin.readUntilDelimiterAlloc(
allocator,
'\n',
1024 * 1024, // máximo 1 MB por linha
) catch |err| {
std.debug.print("Erro: {}\n", .{err});
break;
};
if (std.mem.eql(u8, linha, "sair")) {
allocator.free(linha);
break;
}
try palavras.append(linha);
}
try stdout.print("\nVocê digitou {d} palavras:\n", .{palavras.items.len});
for (palavras.items, 0..) |palavra, i| {
try stdout.print(" {d}. {s}\n", .{ i + 1, palavra });
}
}
Exemplo 3: Leitura de Dados Binários
Lendo um formato binário simples (cabeçalho + dados):
const std = @import("std");
const Cabecalho = packed struct {
magic: u32,
versao: u16,
num_registros: u16,
};
fn lerDadosBinarios(reader: anytype) !void {
// Ler cabeçalho
const cabecalho = try reader.readStruct(Cabecalho);
if (cabecalho.magic != 0x5A494742) { // "ZIGB"
return error.FormatoInvalido;
}
std.debug.print("Versão: {d}, Registros: {d}\n", .{
cabecalho.versao,
cabecalho.num_registros,
});
// Ler cada registro (inteiros de 32 bits)
for (0..cabecalho.num_registros) |i| {
const valor = try reader.readInt(u32, .little);
std.debug.print(" Registro {d}: {d}\n", .{ i, valor });
}
}
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("dados.bin", .{});
defer arquivo.close();
try lerDadosBinarios(arquivo.reader());
}
Padrões Comuns
Leitura Completa para Memória
Para ler um arquivo inteiro de uma vez:
const conteudo = try std.fs.cwd().readFileAlloc(allocator, "config.txt", 1024 * 1024);
defer allocator.free(conteudo);
Iteração com readUntilDelimiterOrEof
O padrão idiomático para processar todas as linhas:
var buf: [4096]u8 = undefined;
while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |linha| {
// processar `linha`
}
// Chegou ao EOF
Combinando Reader com BufferedReader
Sempre que ler de um arquivo ou socket, use buffering para melhor desempenho:
var buffered = std.io.bufferedReader(fonte.reader());
const reader = buffered.reader();
Erros Comuns
error.EndOfStream: Tentou ler mais bytes do que os disponíveis comreadNoEoferror.StreamTooLong: A linha/token excedeu o tamanho do buffer fornecido
Módulos Relacionados
- std.io — Visão geral do módulo de I/O
- std.io.Writer — Interface complementar de escrita
- std.io Buffered — Reader com buffering
- std.io.FixedBufferStream — Reader/Writer sobre buffer fixo
- std.fs.File — Tipo File que implementa Reader