std.io.Writer — Interface de Escrita
A interface Writer do Zig é o mecanismo genérico fundamental para escrita de bytes. Qualquer tipo que implemente a função de escrita adequada pode ser tratado como um Writer, permitindo código reutilizável que funciona com arquivos, sockets, buffers em memória e muito mais.
Visão Geral
O Writer é definido através do tipo parametrizado GenericWriter, que aceita um contexto, um tipo de erro e uma função de escrita:
pub fn GenericWriter(
comptime Context: type,
comptime WriteError: type,
comptime writeFn: fn (Context, []const u8) WriteError!usize,
) type
Essa abordagem permite que o compilador gere código especializado para cada combinação de contexto e função, sem overhead de dispatch dinâmico.
Tipos Principais
GenericWriter
O tipo retornado por GenericWriter(...) possui os seguintes métodos:
Métodos de Escrita Básicos
// Escreve todos os bytes, chamando writeFn múltiplas vezes se necessário
pub fn writeAll(self: Self, bytes: []const u8) WriteError!void
// Escreve um único byte
pub fn writeByte(self: Self, byte: u8) WriteError!void
// Escreve um único byte N vezes
pub fn writeByteNTimes(self: Self, byte: u8, n: usize) WriteError!void
// Escreve um inteiro em formato little-endian ou big-endian
pub fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) WriteError!void
Métodos de Formatação
// Formata e escreve usando a sintaxe de std.fmt
pub fn print(self: Self, comptime fmt: []const u8, args: anytype) WriteError!void
// Escreve uma struct formatada
pub fn writeStruct(self: Self, value: anytype) WriteError!void
AnyWriter
A versão type-erased do Writer, usada quando é necessário armazenar writers de tipos diferentes:
pub const AnyWriter = struct {
context: *const anyopaque,
writeFn: *const fn (*const anyopaque, []const u8) anyerror!usize,
pub fn write(self: AnyWriter, bytes: []const u8) anyerror!usize;
pub fn writeAll(self: AnyWriter, bytes: []const u8) anyerror!void;
pub fn print(self: AnyWriter, comptime fmt: []const u8, args: anytype) anyerror!void;
};
Para converter um writer genérico em AnyWriter:
const any_writer = meu_writer.any();
Exemplo 1: Escrita Formatada
O método print utiliza a mesma sintaxe de formatação de std.fmt:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// Formatação básica
try stdout.print("Texto: {s}\n", .{"Olá, mundo"});
try stdout.print("Inteiro: {d}\n", .{42});
try stdout.print("Float: {d:.2}\n", .{3.14159});
try stdout.print("Hexadecimal: 0x{x}\n", .{@as(u32, 255)});
// Formatação com preenchimento e alinhamento
try stdout.print("|{s:>20}|\n", .{"alinhado à direita"});
try stdout.print("|{s:<20}|\n", .{"alinhado à esquerda"});
try stdout.print("|{s:^20}|\n", .{"centralizado"});
}
Exemplo 2: Função Genérica com Writer
O padrão idiomático do Zig é aceitar anytype para o writer:
const std = @import("std");
const Registro = struct {
nome: []const u8,
idade: u32,
pontuacao: f64,
};
fn escreverCSV(writer: anytype, registros: []const Registro) !void {
// Cabeçalho
try writer.writeAll("nome,idade,pontuacao\n");
// Dados
for (registros) |reg| {
try writer.print("{s},{d},{d:.1}\n", .{ reg.nome, reg.idade, reg.pontuacao });
}
}
pub fn main() !void {
const registros = [_]Registro{
.{ .nome = "Ana", .idade = 28, .pontuacao = 95.5 },
.{ .nome = "Bruno", .idade = 34, .pontuacao = 87.3 },
.{ .nome = "Carla", .idade = 22, .pontuacao = 91.8 },
};
// Escrever para stdout
const stdout = std.io.getStdOut().writer();
try escreverCSV(stdout, ®istros);
// Escrever para buffer em memória
var buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
try escreverCSV(fbs.writer(), ®istros);
std.debug.print("CSV em memória ({d} bytes):\n{s}\n", .{
fbs.getWritten().len,
fbs.getWritten(),
});
}
Exemplo 3: Writer Customizado
Você pode criar seu próprio tipo que implementa a interface Writer:
const std = @import("std");
const ContadorWriter = struct {
bytes_escritos: usize = 0,
inner: std.fs.File.Writer,
const Writer = std.io.GenericWriter(*@This(), std.fs.File.WriteError, write);
fn write(self: *@This(), bytes: []const u8) std.fs.File.WriteError!usize {
const n = try self.inner.write(bytes);
self.bytes_escritos += n;
return n;
}
fn writer(self: *@This()) Writer {
return .{ .context = self };
}
};
pub fn main() !void {
var contador = ContadorWriter{
.inner = std.io.getStdOut().writer(),
};
const w = contador.writer();
try w.print("Linha 1: Olá, mundo!\n", .{});
try w.print("Linha 2: Teste de contagem\n", .{});
std.debug.print("\nTotal de bytes escritos: {d}\n", .{contador.bytes_escritos});
}
Padrões Comuns
Escrita Binária
Para escrever dados binários estruturados:
// Escrever inteiro de 32 bits em little-endian
try writer.writeInt(u32, 0xDEADBEEF, .little);
// Escrever um slice de bytes
try writer.writeAll(&[_]u8{ 0x00, 0x01, 0x02, 0x03 });
Encadeamento com BufferedWriter
Para melhor desempenho em escritas frequentes e pequenas:
var buf_writer = std.io.bufferedWriter(arquivo.writer());
const writer = buf_writer.writer();
// ... múltiplas escritas pequenas ...
try buf_writer.flush(); // Não esqueça de fazer flush!
Usando AnyWriter para Armazenamento
Quando precisar armazenar o writer em uma struct:
const Logger = struct {
writer: std.io.AnyWriter,
pub fn log(self: *Logger, comptime fmt: []const u8, args: anytype) void {
self.writer.print(fmt, args) catch {};
}
};
Módulos Relacionados
- std.io — Visão geral do módulo de I/O
- std.io.Reader — Interface complementar de leitura
- std.io Buffered — Writer com buffering
- std.fmt — Sintaxe de formatação usada por
print - std.fs.File — Tipo File que implementa Writer