std.heap.ArenaAllocator em Zig — Referência e Exemplos

ArenaAllocator — Alocador de Arena

O ArenaAllocator é um alocador que permite liberar toda a memória alocada de uma vez. Chamadas individuais a free() são no-ops — a memória só é efetivamente liberada quando deinit() ou reset() é chamado. Isso o torna extremamente eficiente para alocações temporárias com escopo definido.

Visão Geral

const std = @import("std");

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Libera TODA a memória de uma vez

const allocator = arena.allocator();

O ArenaAllocator é ideal quando:

  • Muitas alocações são feitas e liberadas juntas
  • O tempo de vida das alocações é o mesmo (ex: processamento de uma requisição)
  • Desempenho de alocação é importante
  • Você quer evitar a complexidade de liberar cada alocação individualmente

Métodos

// Inicializa com um alocador subjacente
pub fn init(child_allocator: Allocator) ArenaAllocator

// Libera toda a memória
pub fn deinit(self: *ArenaAllocator) void

// Libera toda a memória mas mantém capacidade para reusar
pub fn reset(self: *ArenaAllocator, mode: ResetMode) void

// Obtém a interface Allocator
pub fn allocator(self: *ArenaAllocator) Allocator

ResetMode

pub const ResetMode = enum {
    free_all,     // Libera toda a memória de volta ao alocador subjacente
    retain_capacity, // Mantém a memória alocada para reuso
};

Exemplo 1: Processamento de Requisição

const std = @import("std");

const Requisicao = struct {
    metodo: []const u8,
    caminho: []const u8,
    headers: std.StringHashMap([]const u8),
};

fn processarRequisicao(arena_alloc: std.mem.Allocator) ![]const u8 {
    // Todas as alocações temporárias usam o arena
    var headers = std.StringHashMap([]const u8).init(arena_alloc);
    try headers.put("Content-Type", "text/html");
    try headers.put("Server", "Zig/0.13");

    // Construir resposta
    const resposta = try std.fmt.allocPrint(arena_alloc,
        "HTTP/1.1 200 OK\nContent-Type: {s}\n\nOlá, mundo!",
        .{headers.get("Content-Type") orelse "text/plain"},
    );

    return resposta;
}

pub fn main() !void {
    // Arena para cada requisição
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const resp = try processarRequisicao(arena.allocator());
    std.debug.print("{s}\n", .{resp});

    // Tudo é liberado automaticamente pelo defer arena.deinit()
    // Nenhum free individual necessário!
}

Exemplo 2: Reset para Reuso em Loop

const std = @import("std");

fn processarItem(allocator: std.mem.Allocator, id: usize) !void {
    // Alocações temporárias para este item
    const nome = try std.fmt.allocPrint(allocator, "item_{d}", .{id});
    const dados = try allocator.alloc(u8, 1024);
    @memset(dados, @intCast(id % 256));

    std.debug.print("Processado: {s} ({d} bytes)\n", .{ nome, dados.len });
    // Não precisa liberar — o arena cuida disso
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    // Processar 10 itens, resetando o arena entre cada um
    for (0..10) |i| {
        // Reset mantém a capacidade para reuso eficiente
        _ = arena.reset(.retain_capacity);

        try processarItem(arena.allocator(), i);
    }
}

Exemplo 3: Arena como Alocador Temporário dentro de Função

const std = @import("std");

fn construirMensagem(allocator: std.mem.Allocator, partes: []const []const u8) ![]u8 {
    // Arena para trabalho temporário dentro da função
    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();
    const temp = arena.allocator();

    // Calcular tamanho total (alocações temporárias no arena)
    var tamanho_total: usize = 0;
    var separadores = try temp.alloc(usize, partes.len);
    for (partes, 0..) |parte, i| {
        separadores[i] = tamanho_total;
        tamanho_total += parte.len;
        if (i < partes.len - 1) tamanho_total += 2; // ", "
    }

    // Resultado final usa o alocador do chamador (não o arena!)
    var resultado = try allocator.alloc(u8, tamanho_total);
    var pos: usize = 0;

    for (partes, 0..) |parte, i| {
        @memcpy(resultado[pos..][0..parte.len], parte);
        pos += parte.len;
        if (i < partes.len - 1) {
            resultado[pos] = ',';
            resultado[pos + 1] = ' ';
            pos += 2;
        }
    }

    return resultado;
    // Arena é liberado aqui, mas resultado sobrevive
}

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

    const partes = [_][]const u8{ "Zig", "é", "incrível" };
    const msg = try construirMensagem(allocator, &partes);
    defer allocator.free(msg);

    std.debug.print("{s}\n", .{msg}); // "Zig, é, incrível"
}

Padrões Comuns

Arena por Escopo

{
    var arena = std.heap.ArenaAllocator.init(base_allocator);
    defer arena.deinit();
    // Todas as alocações dentro deste bloco usam o arena
    try fazerTrabalho(arena.allocator());
}
// Toda memória liberada aqui

Arena para Servidor Web

while (true) {
    const req = try aceitar_requisicao();
    _ = arena.reset(.retain_capacity);
    try processar(arena.allocator(), req);
}

Resultado Longo + Temporários Curtos

fn processar(result_allocator: Allocator) ![]u8 {
    var arena = std.heap.ArenaAllocator.init(result_allocator);
    defer arena.deinit();
    const temp = arena.allocator();

    // Temporários no arena
    const intermediario = try temp.alloc(u8, 1000);
    _ = intermediario;

    // Resultado no alocador do chamador
    return try result_allocator.dupe(u8, "resultado final");
}

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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