Writer Interface em Zig — O que é e Como Usar

Writer Interface em Zig — O que é e Como Usar

Definição

A Writer interface em Zig é o padrão utilizado pela biblioteca padrão para abstração de escrita de dados. Qualquer tipo que forneça uma função write com a assinatura correta pode ser usado como Writer. Isso permite escrever código genérico que funciona com stdout, arquivos, buffers em memória, sockets de rede ou qualquer outro destino de dados.

Em Zig, interfaces são implementadas via anytype (duck typing em comptime) ou via o tipo concreto std.io.Writer, que usa uma vtable para dispatch dinâmico.

Por que Writer Interface Importa

  1. Código genérico: Uma função que aceita anytype writer funciona com qualquer destino.
  2. Testabilidade: Escrever em buffer para testes, em arquivo para produção.
  3. Composição: Writers podem ser encadeados (buffered writer, counting writer, etc.).
  4. Padrão da std lib: Formatação, logging e serialização usam a Writer interface.

Exemplo Prático

Função Genérica com Writer

const std = @import("std");

fn escreverRelatorio(writer: anytype, nome: []const u8, pontos: u32) !void {
    try writer.print("Relatório de: {s}\n", .{nome});
    try writer.print("Pontuação: {}\n", .{pontos});
    try writer.print("Status: {s}\n", .{
        if (pontos >= 70) "Aprovado" else "Reprovado",
    });
    try writer.writeAll("---\n");
}

pub fn main() !void {
    // Escrever para stdout
    const stdout = std.io.getStdOut().writer();
    try escreverRelatorio(stdout, "Ana", 85);
    try escreverRelatorio(stdout, "Bruno", 60);
}

Escrever em Buffer (para Testes)

const std = @import("std");

fn formatarSaudacao(writer: anytype, nome: []const u8) !void {
    try writer.print("Olá, {s}!", .{nome});
}

test "formatar saudação" {
    var buffer: [256]u8 = undefined;
    var stream = std.io.fixedBufferStream(&buffer);
    const writer = stream.writer();

    try formatarSaudacao(writer, "Zig Brasil");

    const resultado = stream.getWritten();
    try std.testing.expectEqualStrings("Olá, Zig Brasil!", resultado);
}

Buffered Writer para Performance

const std = @import("std");

pub fn main() !void {
    const arquivo = try std.fs.cwd().createFile("saida.txt", .{});
    defer arquivo.close();

    // Buffered writer reduz syscalls de escrita
    var buf_writer = std.io.bufferedWriter(arquivo.writer());
    const writer = buf_writer.writer();

    for (0..1000) |i| {
        try writer.print("Linha {}\n", .{i});
    }

    // Flush final para garantir que tudo foi escrito
    try buf_writer.flush();
}

Writers Comuns na std lib

WriterDescrição
std.io.getStdOut().writer()Saída padrão (stdout)
std.io.getStdErr().writer()Saída de erro (stderr)
file.writer()Escrita em arquivo
stream.writer()Escrita em fixedBufferStream
std.io.bufferedWriter(w)Writer com buffer
std.io.countingWriter(w)Writer que conta bytes escritos

Implementando um Writer Customizado

É possível criar seu próprio writer para destinos especiais — por exemplo, um writer que conta bytes, escreve com prefixo de timestamp ou envia dados por uma fila:

const std = @import("std");

// Writer que converte texto para maiúsculas
const MaiusculasContext = struct {
    inner: std.fs.File.Writer,
};

fn writeMaiusculas(ctx: *MaiusculasContext, bytes: []const u8) !usize {
    var buf: [512]u8 = undefined;
    const limite = @min(bytes.len, buf.len);
    for (bytes[0..limite], 0..) |c, i| {
        buf[i] = std.ascii.toUpper(c);
    }
    return ctx.inner.write(buf[0..limite]);
}

Para casos mais simples, countingWriter da std já oferece uma funcionalidade pronta: envolve qualquer writer e expõe bytes_written com o total escrito até o momento.

Comparação com Outras Linguagens

LinguagemAbstração de escritaTipagem
Ziganytype ou std.io.Writer (vtable)Estática (comptime) ou dinâmica
Rusttrait std::io::WriteEstática (generics) ou dyn Write
Gointerface io.WriterDinâmica (interface)
CFILE* ou int fdManual, sem abstração

O Zig oferece o melhor dos dois mundos: dispatch em comptime (zero overhead) com anytype, e dispatch dinâmico (armazenável em structs) com std.io.Writer.

Boas Práticas

  • Prefira anytype em funções livres: Para funções que apenas escrevem e não armazenam o writer, anytype tem custo zero e aceita qualquer writer sem boxing.
  • Use std.io.Writer em structs: Quando precisar guardar um writer como campo de struct, use o tipo concreto std.io.Writer para despacho dinâmico via vtable.
  • Sempre faça flush explícito: Mesmo que seu código “funcione” sem flush, buffers não esvaziados podem perder dados em caso de crash. Combine defer buf_writer.flush() com tratamento de erro.
  • Trate erros de I/O: Escrita pode falhar por disco cheio, socket encerrado, ou limite de processo. Propague sempre com try.

Armadilhas Comuns

  • Flush: Buffered writers exigem flush() no final para garantir que dados pendentes sejam escritos.
  • Erros de escrita: Sempre trate os erros com try. Escrever pode falhar (disco cheio, socket fechado).
  • anytype vs Writer concreto: Use anytype para funções simples. Use std.io.Writer quando precisar armazenar o writer em uma struct.
  • Performance: Sem buffer, cada print pode gerar uma syscall. Use bufferedWriter para escrita intensiva.

Termos Relacionados

Tutoriais Relacionados

Continue aprendendo Zig

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