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" };
}

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.