std.io.Writer em Zig — Referência e Exemplos

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, &registros);

    // Escrever para buffer em memória
    var buf: [4096]u8 = undefined;
    var fbs = std.io.fixedBufferStream(&buf);
    try escreverCSV(fbs.writer(), &registros);

    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

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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