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
- Código genérico: Uma função que aceita
anytypewriter funciona com qualquer destino. - Testabilidade: Escrever em buffer para testes, em arquivo para produção.
- Composição: Writers podem ser encadeados (buffered writer, counting writer, etc.).
- 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
| Writer | Descriçã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
| Linguagem | Abstração de escrita | Tipagem |
|---|---|---|
| Zig | anytype ou std.io.Writer (vtable) | Estática (comptime) ou dinâmica |
| Rust | trait std::io::Write | Estática (generics) ou dyn Write |
| Go | interface io.Writer | Dinâmica (interface) |
| C | FILE* ou int fd | Manual, 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
anytypeem funções livres: Para funções que apenas escrevem e não armazenam o writer,anytypetem custo zero e aceita qualquer writer sem boxing. - Use
std.io.Writerem structs: Quando precisar guardar um writer como campo de struct, use o tipo concretostd.io.Writerpara despacho dinâmico via vtable. - Sempre faça
flushexplícito: Mesmo que seu código “funcione” sem flush, buffers não esvaziados podem perder dados em caso de crash. Combinedefer 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
anytypepara funções simples. Usestd.io.Writerquando precisar armazenar o writer em uma struct. - Performance: Sem buffer, cada
printpode gerar uma syscall. UsebufferedWriterpara escrita intensiva.
Termos Relacionados
- Reader Interface — Interface de leitura complementar
- anytype — Mecanismo de genéricos usado pela interface
- Vtable Interface — Dispatch dinâmico para armazenar writers
- Error Union — Writers retornam
!voidou!usize