---
title: "Cheatsheet: Observer em Zig"
url: "https://ziglang.com.br/padroes/cheatsheet-observer-em-zig/"
markdown_url: "https://ziglang.com.br/padroes/cheatsheet-observer-em-zig.MD"
description: "Design pattern Observer implementado em Zig: sistema de eventos, notificação de mudanças, pub/sub com comptime e function pointers. Guia completo em português."
date: "2026-02-21"
author: "Zig Brasil"
---

# Cheatsheet: Observer em Zig

Design pattern Observer implementado em Zig: sistema de eventos, notificação de mudanças, pub/sub com comptime e function pointers. Guia completo em português.


# Observer em Zig

O padrão Observer define uma dependência um-para-muitos entre objetos, de modo que quando um objeto (o sujeito) muda de estado, todos os seus dependentes (observadores) são notificados automaticamente. Em Zig, esse padrão é implementado de forma leve usando **slices de ponteiros de função** ou **interfaces via comptime**.

## Quando Usar

- Sistemas de eventos e callbacks
- Notificação de mudanças de estado (UI, dados, config)
- Logging e monitoramento desacoplados
- Comunicação entre módulos sem acoplamento direto

## Implementação com Ponteiros de Função

```zig
const std = @import("std");

fn EventEmitter(comptime EventType: type) type {
    return struct {
        const Self = @This();
        const Callback = *const fn (EventType) void;

        listeners: std.ArrayList(Callback),

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{
                .listeners = std.ArrayList(Callback).init(allocator),
            };
        }

        pub fn deinit(self: *Self) void {
            self.listeners.deinit();
        }

        pub fn on(self: *Self, callback: Callback) !void {
            try self.listeners.append(callback);
        }

        pub fn off(self: *Self, callback: Callback) void {
            for (self.listeners.items, 0..) |cb, i| {
                if (cb == callback) {
                    _ = self.listeners.orderedRemove(i);
                    return;
                }
            }
        }

        pub fn emit(self: *const Self, evento: EventType) void {
            for (self.listeners.items) |callback| {
                callback(evento);
            }
        }
    };
}

// Eventos do sistema
const Evento = union(enum) {
    usuario_logou: []const u8,
    usuario_saiu: []const u8,
    erro: []const u8,
};

fn logHandler(evento: Evento) void {
    switch (evento) {
        .usuario_logou => |nome| std.debug.print("[LOG] {s} entrou\n", .{nome}),
        .usuario_saiu => |nome| std.debug.print("[LOG] {s} saiu\n", .{nome}),
        .erro => |msg| std.debug.print("[ERRO] {s}\n", .{msg}),
    }
}

fn metricasHandler(evento: Evento) void {
    switch (evento) {
        .usuario_logou => std.debug.print("[METRICS] +1 usuário online\n", .{}),
        .usuario_saiu => std.debug.print("[METRICS] -1 usuário online\n", .{}),
        .erro => std.debug.print("[METRICS] +1 erro registrado\n", .{}),
    }
}

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

    var emitter = EventEmitter(Evento).init(gpa.allocator());
    defer emitter.deinit();

    try emitter.on(logHandler);
    try emitter.on(metricasHandler);

    emitter.emit(.{ .usuario_logou = "Ana" });
    emitter.emit(.{ .usuario_saiu = "Carlos" });
    emitter.emit(.{ .erro = "Conexão perdida" });
}
```

## Observer com Contexto

```zig
const std = @import("std");

const Observador = struct {
    ptr: *anyopaque,
    notificarFn: *const fn (*anyopaque, []const u8) void,

    pub fn notificar(self: Observador, mensagem: []const u8) void {
        self.notificarFn(self.ptr, mensagem);
    }
};

const Sujeito = struct {
    observadores: std.ArrayList(Observador),
    estado: []const u8,

    pub fn init(allocator: std.mem.Allocator) Sujeito {
        return .{
            .observadores = std.ArrayList(Observador).init(allocator),
            .estado = "",
        };
    }

    pub fn deinit(self: *Sujeito) void {
        self.observadores.deinit();
    }

    pub fn registrar(self: *Sujeito, obs: Observador) !void {
        try self.observadores.append(obs);
    }

    pub fn setEstado(self: *Sujeito, novo_estado: []const u8) void {
        self.estado = novo_estado;
        self.notificarTodos();
    }

    fn notificarTodos(self: *const Sujeito) void {
        for (self.observadores.items) |obs| {
            obs.notificar(self.estado);
        }
    }
};
```

## Observer com Filtro por Tipo de Evento

Em sistemas maiores, os listeners frequentemente se interessam apenas por certos tipos de evento. Evite que cada listener filtre manualmente:

```zig
const std = @import("std");

fn EventBus(comptime E: type) type {
    return struct {
        const Self = @This();
        const Tag = std.meta.Tag(E);
        const Callback = struct {
            tag: Tag,
            funcao: *const fn (E) void,
        };

        callbacks: std.ArrayList(Callback),

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{ .callbacks = std.ArrayList(Callback).init(allocator) };
        }

        pub fn deinit(self: *Self) void {
            self.callbacks.deinit();
        }

        pub fn on(self: *Self, tag: Tag, callback: *const fn (E) void) !void {
            try self.callbacks.append(.{ .tag = tag, .funcao = callback });
        }

        pub fn emit(self: *const Self, evento: E) void {
            const tag_atual = std.meta.activeTag(evento);
            for (self.callbacks.items) |cb| {
                if (cb.tag == tag_atual) cb.funcao(evento);
            }
        }
    };
}
```

Agora cada listener registra interesse em um tipo específico de evento, e o bus despacha apenas para os listeners relevantes.

## Considerações de Performance

- **Lista de listeners com busca linear**: o `emit` itera sobre todos os listeners para cada evento. Para poucos listeners (< 20), o custo é desprezível. Para sistemas de alta frequência com muitos listeners, considere um HashMap de `Tag -> []Callback` para O(1) de lookup.
- **Ponteiros de função vs closures**: Zig não tem closures nativas. Se o listener precisa de contexto (estado), use o padrão `Observador` com `*anyopaque` e ponteiro de função — isso permite que o listener acesse seu próprio estado.
- **Evite emitir eventos dentro de handlers**: se um handler emite outro evento durante o `emit`, e este novo evento dispara mais handlers, você pode acumular uma stack profunda ou entrar em loop infinito. Enfileire eventos secundários para emissão depois que o `emit` atual terminar.
- **Thread safety**: `std.ArrayList` não é thread-safe. Se produtores emitem eventos em uma thread enquanto outra thread registra listeners, adicione um `Mutex` ao redor das operações no ArrayList.

## Erros Comuns

**Não remover listeners após o objeto ser destruído**: se um objeto se registra como observer e é destruído sem chamar `off`, o emitter vai chamar um ponteiro de função inválido. Sempre chame `emitter.off(callback)` no `deinit` do observer.

**Modificar a lista de listeners durante o `emit`**: se um callback chama `emitter.on` ou `emitter.off` durante o `emit`, o ArrayList pode ser realocado ou modificado enquanto está sendo iterado. Mantenha uma cópia do slice de listeners antes de iterar, ou use uma flag `emitindo` para adiar modificações.

**Eventos com payloads grandes por valor**: se o tipo de evento contém strings longas ou arrays grandes, cada `emit` copia o evento para cada callback. Use ponteiros (`*const Evento`) para eventos grandes ou prefira payloads pequenos e específicos.

## Perguntas Frequentes

**Qual é a diferença entre Observer e Pub/Sub?**
Observer geralmente implica que o sujeito conhece seus observers diretamente (lista de callbacks). Pub/Sub introduz um broker intermediário que desacopla completamente publicador e assinante — o publicador não sabe quem assina. O `EventBus` acima é um Pub/Sub simples.

**Como garantir ordem de notificação determinística?**
Os observers são notificados na ordem de registro. Se a ordem importa, documente isso e respeite a ordem de chamadas a `on`. Alternativamente, adicione uma prioridade numérica ao callback e ordene a lista antes de emitir.

**Como testar código que emite eventos?**
Registre um callback de teste que armazena os eventos recebidos em um `ArrayList`, verifique a lista após o `emit`. Use `std.testing.allocator` para o ArrayList de listeners do emitter e o ArrayList de captura do teste — ambos vão detectar leaks automaticamente.

## Quando Evitar

- Quando há apenas um listener (callback direto é mais simples)
- Em código que precisa de performance extrema no hot path
- Quando a ordem de notificação importa e é difícil de controlar
- Riscos de referência circular entre observer e subject

## Veja Também

- [Strategy](/padroes/strategy/) — Trocar comportamento em runtime
- [Command](/padroes/command/) — Encapsular ações como objetos
- [Producer-Consumer](/padroes/producer-consumer/) — Comunicação assíncrona
- [Concorrência](/cheatsheets/concorrencia/) — Observer thread-safe
- [Receitas](/receitas/) — Exemplos práticos de eventos
