---
title: "Cheatsheet: Pool de Objetos em Zig"
url: "https://ziglang.com.br/padroes/cheatsheet-pool-de-objetos-em-zig/"
markdown_url: "https://ziglang.com.br/padroes/cheatsheet-pool-de-objetos-em-zig.MD"
description: "Design pattern Pool de Objetos implementado em Zig: reutilização de objetos pré-alocados, evitar alocações frequentes, pools thread-safe. Guia completo em português."
date: "2026-02-21"
author: "Zig Brasil"
---

# Cheatsheet: Pool de Objetos em Zig

Design pattern Pool de Objetos implementado em Zig: reutilização de objetos pré-alocados, evitar alocações frequentes, pools thread-safe. Guia completo em português.


# Pool de Objetos em Zig

O padrão Pool de Objetos mantém uma coleção de objetos pré-alocados e reutilizáveis, evitando o custo de alocação e desalocação frequente. Em Zig, com controle explícito de memória, este padrão é especialmente útil para aplicações de alta performance, servidores e sistemas embarcados.

## Quando Usar

- Servidores que criam/destroem conexões frequentemente
- Game loops com criação/destruição de entidades
- Pools de buffers para I/O
- Sistemas embarcados com memória limitada
- Evitar fragmentação de memória

## Implementação Básica

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

fn Pool(comptime T: type, comptime tamanho: usize) type {
    return struct {
        const Self = @This();

        itens: [tamanho]T = undefined,
        disponiveis: [tamanho]bool = [_]bool{true} ** tamanho,
        inicializador: *const fn () T,

        pub fn init(inicializador: *const fn () T) Self {
            var self = Self{
                .inicializador = inicializador,
            };
            for (&self.itens) |*item| {
                item.* = inicializador();
            }
            return self;
        }

        pub fn adquirir(self: *Self) ?*T {
            for (self.disponiveis, 0..) |*disp, i| {
                if (disp.*) {
                    disp.* = false;
                    return &self.itens[i];
                }
            }
            return null; // pool esgotado
        }

        pub fn liberar(self: *Self, ptr: *T) void {
            const inicio = @intFromPtr(&self.itens[0]);
            const endereco = @intFromPtr(ptr);
            const indice = (endereco - inicio) / @sizeOf(T);
            if (indice < tamanho) {
                self.itens[indice] = (self.inicializador)(); // resetar
                self.disponiveis[indice] = true;
            }
        }

        pub fn disponiveisCount(self: *const Self) usize {
            var count: usize = 0;
            for (self.disponiveis) |d| {
                if (d) count += 1;
            }
            return count;
        }
    };
}

const Buffer = struct {
    dados: [1024]u8 = undefined,
    tamanho: usize = 0,

    fn criar() Buffer {
        return Buffer{};
    }
};

pub fn main() void {
    var pool = Pool(Buffer, 16).init(Buffer.criar);

    // Adquirir buffer do pool
    if (pool.adquirir()) |buf| {
        buf.dados[0] = 'A';
        buf.tamanho = 1;

        std.debug.print("Buffer adquirido, disponíveis: {d}\n", .{pool.disponiveisCount()});

        // Devolver ao pool quando terminar
        pool.liberar(buf);
        std.debug.print("Buffer liberado, disponíveis: {d}\n", .{pool.disponiveisCount()});
    }
}
```

## Pool com Allocator Dinâmico

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

fn PoolDinamico(comptime T: type) type {
    return struct {
        const Self = @This();

        livres: std.ArrayList(*T),
        allocator: std.mem.Allocator,
        total_criados: usize = 0,
        max_tamanho: usize,

        pub fn init(allocator: std.mem.Allocator, max: usize) Self {
            return .{
                .livres = std.ArrayList(*T).init(allocator),
                .allocator = allocator,
                .max_tamanho = max,
            };
        }

        pub fn deinit(self: *Self) void {
            for (self.livres.items) |item| {
                self.allocator.destroy(item);
            }
            self.livres.deinit();
        }

        pub fn adquirir(self: *Self) !*T {
            if (self.livres.popOrNull()) |item| {
                return item;
            }
            // Criar novo se abaixo do limite
            if (self.total_criados < self.max_tamanho) {
                const novo = try self.allocator.create(T);
                novo.* = std.mem.zeroes(T);
                self.total_criados += 1;
                return novo;
            }
            return error.PoolEsgotado;
        }

        pub fn liberar(self: *Self, item: *T) !void {
            item.* = std.mem.zeroes(T); // resetar
            try self.livres.append(item);
        }
    };
}
```

## Pool com FixedBufferAllocator para Sistemas Embarcados

Em sistemas com memória estática, você pode criar um pool sem alocação dinâmica alguma:

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

fn PoolEstatico(comptime T: type, comptime N: usize) type {
    return struct {
        const Self = @This();

        itens: [N]T = undefined,
        em_uso: std.StaticBitSet(N) = std.StaticBitSet(N).initEmpty(),

        pub fn adquirir(self: *Self) ?*T {
            const idx = self.em_uso.findFirstUnset() orelse return null;
            self.em_uso.set(idx);
            self.itens[idx] = std.mem.zeroes(T);
            return &self.itens[idx];
        }

        pub fn liberar(self: *Self, ptr: *T) void {
            const base = @intFromPtr(&self.itens[0]);
            const addr = @intFromPtr(ptr);
            const idx = (addr - base) / @sizeOf(T);
            std.debug.assert(idx < N);
            self.em_uso.unset(idx);
        }

        pub fn emUso(self: *const Self) usize {
            return self.em_uso.count();
        }
    };
}

// Uso: pool completamente estático, sem heap
var pool_conexoes = PoolEstatico(Conexao, 32){};
```

`std.StaticBitSet` é muito mais eficiente que o array de `bool` para verificar disponibilidade — especialmente para pools grandes, onde o bit set cabe em alguns registradores.

## Considerações de Performance

- **Pool estático vs dinâmico**: o pool estático (tamanho fixo em comptime) não faz nenhuma syscall e vive completamente no segmento de dados ou stack. Para servidores com carga previsível, isso elimina toda latência de alocação.
- **Resetar o estado ao liberar**: chamar `std.mem.zeroes(T)` ao adquirir garante que o objeto começa em estado limpo. Se o objeto tem campos que não precisam ser zerados (ex: buffers que serão completamente reescritos), você pode pular o zero para ganhar performance.
- **Fragmentação não existe em pools**: ao contrário de um allocator de propósito geral, o pool não fragmenta — todos os slots têm o mesmo tamanho. Isso torna o comportamento de memória completamente previsível ao longo do tempo.
- **`StaticBitSet.findFirstUnset` é O(N/wordsize)**: para pools de até 64 objetos, encontrar um slot livre leva apenas uma instrução de bit (`tzcnt`). Para pools maiores, ainda é muito eficiente.

## Erros Comuns

**Retornar um objeto ao pool errado**: se você tem dois pools do mesmo tipo e retorna um ponteiro ao pool errado, o índice calculado será inválido. Adicione uma asserção: `std.debug.assert(addr >= base and addr < base + N * @sizeOf(T))`.

**Usar o objeto após liberá-lo**: após `pool.liberar(ptr)`, o ponteiro `ptr` pode ser adquirido por outro cliente. Qualquer acesso após a liberação é undefined behavior. Considere anular o ponteiro após liberar: `defer { pool.liberar(conn); conn = undefined; }`.

**Pool muito pequeno causando `null` inesperado**: dimensione o pool com folga. Meça o pico real de uso concorrente em produção e adicione 20-30% de margem. Um `adquirir` que retorna `null` em produção é tão problemático quanto um `OutOfMemory`.

## Perguntas Frequentes

**Quando usar Pool de Objetos vs Arena Allocator?**
Use Pool de Objetos quando você precisa liberar objetos individualmente em momentos diferentes. Use Arena quando você cria muitos objetos e libera todos ao mesmo tempo ao final de uma operação (ex: processar uma requisição HTTP).

**Como tornar o pool thread-safe?**
Adicione um `std.Thread.Mutex` ao pool. Bloqueie para `adquirir` e `liberar`. Para alta contenção, considere pools por thread (thread-local storage) com um pool central de reserva — padrão usado em alocadores de alta performance como `jemalloc`.

**O pool garante que objetos são inicializados corretamente?**
Apenas se você inicializar no `adquirir`. O pool é responsável pela *alocação*, não pela *inicialização semântica*. Para objetos que precisam de setup complexo (`init` com alocações), crie-os via `init` após adquirir o slot do pool.

## Quando Evitar

- Objetos pequenos e baratos de criar (overhead do pool supera o benefício)
- Quando o tamanho do pool é difícil de prever
- Objetos com estado complexo difícil de resetar
- Aplicações onde memória não é gargalo

## Veja Também

- [Arena Pattern](/padroes/arena-pattern/) — Alocação em bloco sem reutilização individual
- [Allocators](/cheatsheets/allocators/) — Alocadores de memória em Zig
- [Flyweight](/padroes/flyweight/) — Compartilhar estado entre objetos
- [Concorrência](/cheatsheets/concorrencia/) — Pool thread-safe
- [FAQ Performance](/faq/faq-performance/) — Otimização de memória
