Cheatsheet: Decorator em Zig

Decorator em Zig

O padrão Decorator permite adicionar comportamento a objetos individualmente sem afetar outros objetos da mesma classe. Em Zig, este padrão é naturalmente implementado com composição e wrapping, especialmente visível no design de readers/writers da biblioteca padrão que são compostos em camadas.

Quando Usar

  • Adicionar logging, métricas ou caching a operações existentes
  • Compor camadas de I/O (buffering, compressão, criptografia)
  • Middleware em servidores web
  • Adicionar validação ou transformação sem alterar o código original

Writer Decorado

const std = @import("std");

fn LoggingWriter(comptime WriterType: type) type {
    return struct {
        const Self = @This();
        writer_interno: WriterType,
        bytes_escritos: *usize,

        pub const Error = WriterType.Error;
        pub const Writer = std.io.Writer(*Self, Error, write);

        pub fn writer(self: *Self) Writer {
            return .{ .context = self };
        }

        fn write(self: *Self, bytes: []const u8) Error!usize {
            const n = try self.writer_interno.write(bytes);
            self.bytes_escritos.* += n;
            return n;
        }
    };
}

fn loggingWriter(inner_writer: anytype, bytes_counter: *usize) LoggingWriter(@TypeOf(inner_writer)) {
    return .{
        .writer_interno = inner_writer,
        .bytes_escritos = bytes_counter,
    };
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var bytes_total: usize = 0;
    var logging = loggingWriter(stdout, &bytes_total);
    const writer = logging.writer();

    try writer.writeAll("Olá, mundo!\n");
    try writer.print("Número: {d}\n", .{42});

    // O decorator contou os bytes sem alterar o comportamento do writer
    std.debug.print("Total de bytes escritos: {d}\n", .{bytes_total});
}

Decorator de Função

const std = @import("std");

fn comTiming(comptime func: anytype) @TypeOf(func) {
    return struct {
        fn wrapper(args: anytype) @typeInfo(@TypeOf(func)).@"fn".return_type.? {
            var timer = std.time.Timer.start() catch unreachable;

            const resultado = @call(.auto, func, args);

            const elapsed = timer.read();
            std.debug.print("[TIMING] {d}ms\n", .{elapsed / std.time.ns_per_ms});

            return resultado;
        }
    }.wrapper;
}

fn operacaoLenta() void {
    std.time.sleep(50 * std.time.ns_per_ms);
}

// const operacaoComTiming = comTiming(operacaoLenta);

Middleware Pattern

const std = @import("std");

const Requisicao = struct {
    path: []const u8,
    metodo: []const u8,
    headers: std.StringHashMap([]const u8),
};

const Resposta = struct {
    status: u16 = 200,
    corpo: []const u8 = "",
};

const Handler = *const fn (*Requisicao) Resposta;
const Middleware = *const fn (*Requisicao, Handler) Resposta;

fn comLog(req: *Requisicao, proximo: Handler) Resposta {
    std.debug.print("[LOG] {s} {s}\n", .{ req.metodo, req.path });
    const resp = proximo(req);
    std.debug.print("[LOG] Status: {d}\n", .{resp.status});
    return resp;
}

fn comAuth(req: *Requisicao, proximo: Handler) Resposta {
    if (!req.headers.contains("Authorization")) {
        return .{ .status = 401, .corpo = "Não autorizado" };
    }
    return proximo(req);
}

fn handlerFinal(req: *Requisicao) Resposta {
    _ = req;
    return .{ .status = 200, .corpo = "OK" };
}

Decorator de Validação com Error Union

Um caso de uso prático é decorar funções de processamento com validação prévia, tirando proveito de error unions:

const std = @import("std");

fn comValidacao(
    comptime T: type,
    comptime validar: fn (T) bool,
    comptime processar: fn (T) !void,
) fn (T) !void {
    return struct {
        fn wrapper(entrada: T) !void {
            if (!validar(entrada)) return error.EntradaInvalida;
            return processar(entrada);
        }
    }.wrapper;
}

fn emailValido(email: []const u8) bool {
    return std.mem.indexOfScalar(u8, email, '@') != null;
}

fn enviarEmail(email: []const u8) !void {
    std.debug.print("Enviando para: {s}\n", .{email});
}

const enviarComValidacao = comValidacao([]const u8, emailValido, enviarEmail);

pub fn main() !void {
    try enviarComValidacao("usuario@exemplo.com"); // ok
    enviarComValidacao("email-invalido") catch |err| {
        std.debug.print("Erro: {}\n", .{err}); // error.EntradaInvalida
    };
}

Considerações de Performance

  • Writers compostos da stdlib: std.io.bufferedWriter e companhia usam exatamente o padrão Decorator. O compilador Zig é capaz de inlinar chamadas em cadeias de writers genéricos (comptime WriterType), eliminando o overhead de indireção quando os tipos são conhecidos em compile time.
  • Decorator com anyopaque: se você usar ponteiros opacos para composição em runtime (como no middleware HTTP), há uma indireção por chamada. Aceitável para código de servidor, mas evite em loops de processamento de alta frequência.
  • Contar bytes e logging: decorators simples como LoggingWriter têm overhead de uma adição e uma chamada de print por write. Para logs de debug, use if (builtin.mode == .Debug) para eliminar o decorator completamente em builds de produção.

Erros Comuns

Propagar o tipo de erro incorretamente: ao criar um LoggingWriter, o Error do decorator deve ser exatamente igual ao Error do writer interno. Se você adicionar error.FalhaNaMetrica ao conjunto de erros, o caller vai precisar tratar um erro que nunca ocorre na prática.

Esquecer de propagar o retorno do writer interno: no método write, sempre retorne o valor n do writer interno — não retorne bytes.len. O writer interno pode ter escrito menos bytes que o solicitado, e o caller precisa saber disso.

Criar decorators com estado mutável compartilhado: se o decorator guarda um contador ou buffer, e múltiplas goroutines usam o mesmo writer decorado, você precisa de sincronização. A alternativa mais simples em Zig é criar um decorator por thread.

Perguntas Frequentes

Qual é a diferença entre Decorator e Wrapper? Na prática, são a mesma coisa em Zig. “Decorator” enfatiza a adição de comportamento mantendo a mesma interface. “Wrapper” é o termo mais genérico para qualquer struct que envolve outra.

Posso decorar structs que não implementam a interface Writer? Sim — o padrão funciona para qualquer interface. Crie seu próprio tipo de “interface” com ptr: *anyopaque e ponteiros de função, e aplique o mesmo padrão de composição. Veja o padrão Type Erasure para a técnica completa.

Como depurar uma cadeia profunda de decorators? Adicione um campo nome: []const u8 a cada decorator e imprima-o nos métodos. Em modo de debug, isso ajuda a rastrear por onde os dados passam. Remova antes de produção ou guarde por trás de if (builtin.mode == .Debug).

Quando Evitar

  • Quando apenas uma variante é necessária (composição direta é mais simples)
  • Muitas camadas de decoração que dificultam depuração
  • Quando performance é crítica e cada camada adiciona overhead

Veja Também

Continue aprendendo Zig

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