Allocator em Zig — O que é e Como Usar

Allocator em Zig — O que é e Como Usar

Definição

Um Allocator (alocador) em Zig é uma abstração que encapsula a lógica de alocação e desalocação de memória. Diferentemente de linguagens como C, onde você usa diretamente malloc e free, ou de linguagens com garbage collector como Java e Go, Zig oferece uma interface padronizada (std.mem.Allocator) que permite trocar a estratégia de alocação sem alterar o código que a consome.

Em termos simples, o allocator é um “objeto” que sabe como pedir memória ao sistema e devolvê-la. Toda função da biblioteca padrão que precisa alocar memória recebe um allocator como parâmetro — essa é uma decisão de design fundamental da linguagem.

Por que Allocators Importam

  1. Controle explícito: Você sempre sabe onde e quando memória é alocada.
  2. Testabilidade: Em testes, você pode usar um allocator que detecta memory leaks automaticamente.
  3. Performance: Diferentes situações pedem diferentes estratégias (arena, pool, page).
  4. Sem alocação oculta: Nenhuma função aloca memória “por baixo dos panos” sem que você saiba.

Exemplo Prático

const std = @import("std");

pub fn main() !void {
    // Criando um General Purpose Allocator
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) @panic("Vazamento de memória detectado!");
    }

    const allocator = gpa.allocator();

    // Alocando uma lista dinâmica
    var lista = std.ArrayList(u32).init(allocator);
    defer lista.deinit();

    try lista.append(10);
    try lista.append(20);
    try lista.append(30);

    for (lista.items) |valor| {
        std.debug.print("Valor: {}\n", .{valor});
    }
}

Neste exemplo, o GeneralPurposeAllocator é criado, e sua interface allocator() é passada para o ArrayList. O defer garante que a memória será liberada ao final do escopo.

Como Funciona a Interface

A interface std.mem.Allocator define três operações fundamentais:

// Simplificação da interface
pub const Allocator = struct {
    allocFn: fn (self: *anyopaque, len: usize, ...) ?[*]u8,
    resizeFn: fn (self: *anyopaque, buf: []u8, new_len: usize, ...) ?usize,
    freeFn: fn (self: *anyopaque, buf: []u8, ...) void,
};

Qualquer tipo que implemente essas funções pode ser usado como allocator. Isso permite criar alocadores personalizados para situações específicas.

Armadilhas Comuns

  • Esquecer o defer deinit(): Sempre que alocar, garanta a desalocação com defer. Sem isso, você terá vazamentos de memória.
  • Usar o allocator errado: Em contextos de alta performance com muitas alocações curtas, prefira um ArenaAllocator em vez do GeneralPurposeAllocator.
  • Misturar allocators: Não aloque com um allocator e desaloque com outro. O comportamento é indefinido.
  • Ignorar falhas de alocação: alloc() retorna um error union. Sempre trate o possível erro com try ou catch.

Tipos de Allocator Disponíveis

AllocatorUso recomendado
GeneralPurposeAllocatorUso geral, com detecção de bugs
ArenaAllocatorMuitas alocações, uma única liberação
FixedBufferAllocatorSem alocação do sistema, usa buffer fixo
page_allocatorAlocações grandes, alinhadas a páginas

Termos Relacionados

Tutoriais Relacionados

Continue aprendendo Zig

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