Cheatsheet: Command em Zig

Command em Zig

O padrão Command encapsula uma requisição como um objeto, permitindo parametrizar, enfileirar, registrar e desfazer operações. Em Zig, isso é implementado com tagged unions (para conjunto fixo de comandos) ou interfaces via ponteiros de função (para extensibilidade).

Quando Usar

  • Sistemas de undo/redo (editores, jogos)
  • Filas de tarefas e job queues
  • Logging de operações para replay
  • Macros e gravação de ações do usuário
  • Transações que precisam ser revertíveis

Implementação com Tagged Union

const std = @import("std");

const Comando = union(enum) {
    inserir: struct { posicao: usize, texto: []const u8 },
    deletar: struct { posicao: usize, tamanho: usize },
    substituir: struct { posicao: usize, antigo: []const u8, novo: []const u8 },

    pub fn executar(self: Comando, buffer: *std.ArrayList(u8)) !void {
        switch (self) {
            .inserir => |cmd| {
                try buffer.insertSlice(cmd.posicao, cmd.texto);
            },
            .deletar => |cmd| {
                buffer.replaceRange(cmd.posicao, cmd.tamanho, &.{});
            },
            .substituir => |cmd| {
                buffer.replaceRange(cmd.posicao, cmd.antigo.len, cmd.novo);
            },
        }
    }

    pub fn desfazer(self: Comando, buffer: *std.ArrayList(u8)) !void {
        switch (self) {
            .inserir => |cmd| {
                buffer.replaceRange(cmd.posicao, cmd.texto.len, &.{});
            },
            .deletar => |cmd| {
                _ = cmd;
                // Precisaria do texto deletado salvo para desfazer
            },
            .substituir => |cmd| {
                buffer.replaceRange(cmd.posicao, cmd.novo.len, cmd.antigo);
            },
        }
    }
};

const EditorHistorico = struct {
    historico: std.ArrayList(Comando),
    posicao: usize = 0,

    pub fn init(allocator: std.mem.Allocator) EditorHistorico {
        return .{
            .historico = std.ArrayList(Comando).init(allocator),
        };
    }

    pub fn deinit(self: *EditorHistorico) void {
        self.historico.deinit();
    }

    pub fn executar(self: *EditorHistorico, cmd: Comando, buffer: *std.ArrayList(u8)) !void {
        try cmd.executar(buffer);
        // Descartar comandos após posição atual (novo branch)
        self.historico.shrinkRetainingCapacity(self.posicao);
        try self.historico.append(cmd);
        self.posicao += 1;
    }

    pub fn desfazer(self: *EditorHistorico, buffer: *std.ArrayList(u8)) !void {
        if (self.posicao == 0) return;
        self.posicao -= 1;
        try self.historico.items[self.posicao].desfazer(buffer);
    }

    pub fn refazer(self: *EditorHistorico, buffer: *std.ArrayList(u8)) !void {
        if (self.posicao >= self.historico.items.len) return;
        try self.historico.items[self.posicao].executar(buffer);
        self.posicao += 1;
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    var buffer = std.ArrayList(u8).init(alloc);
    defer buffer.deinit();

    var editor = EditorHistorico.init(alloc);
    defer editor.deinit();

    try editor.executar(.{ .inserir = .{ .posicao = 0, .texto = "Olá Mundo" } }, &buffer);
    std.debug.print("Após inserir: '{s}'\n", .{buffer.items});

    try editor.desfazer(&buffer);
    std.debug.print("Após desfazer: '{s}'\n", .{buffer.items});

    try editor.refazer(&buffer);
    std.debug.print("Após refazer: '{s}'\n", .{buffer.items});
}

Fila de Comandos

const std = @import("std");

const Tarefa = union(enum) {
    enviar_email: struct { para: []const u8, assunto: []const u8 },
    processar_imagem: struct { caminho: []const u8, largura: u32 },
    gerar_relatorio: struct { periodo: []const u8 },

    pub fn executar(self: Tarefa) void {
        switch (self) {
            .enviar_email => |t| std.debug.print("Enviando email para {s}: {s}\n", .{ t.para, t.assunto }),
            .processar_imagem => |t| std.debug.print("Processando {s} ({d}px)\n", .{ t.caminho, t.largura }),
            .gerar_relatorio => |t| std.debug.print("Gerando relatório: {s}\n", .{t.periodo}),
        }
    }
};

const FilaTarefas = struct {
    fila: std.ArrayList(Tarefa),

    pub fn init(allocator: std.mem.Allocator) FilaTarefas {
        return .{ .fila = std.ArrayList(Tarefa).init(allocator) };
    }

    pub fn deinit(self: *FilaTarefas) void {
        self.fila.deinit();
    }

    pub fn enfileirar(self: *FilaTarefas, tarefa: Tarefa) !void {
        try self.fila.append(tarefa);
    }

    pub fn processarTodas(self: *FilaTarefas) void {
        for (self.fila.items) |tarefa| {
            tarefa.executar();
        }
        self.fila.clearRetainingCapacity();
    }
};

Quando Evitar

  • Operações simples sem necessidade de undo ou enfileiramento
  • Quando o overhead de encapsular cada ação não se justifica
  • Sistemas onde a ordem de execução não importa

Veja Também

Continue aprendendo Zig

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