std.mem.Allocator em Zig — Referência e Exemplos

std.mem.Allocator — Interface de Alocação

A interface Allocator é o mecanismo central de alocação de memória em Zig. Em vez de usar uma função global como malloc, o Zig requer que alocadores sejam passados explicitamente, tornando a gestão de memória transparente e testável.

Visão Geral

Todo código que precisa alocar memória dinâmica recebe um Allocator como parâmetro. Isso permite:

  • Transparência: Fica claro quais funções alocam memória
  • Testabilidade: Testes podem usar alocadores que detectam vazamentos
  • Flexibilidade: O chamador escolhe a estratégia de alocação
const std = @import("std");
const Allocator = std.mem.Allocator;

A Interface

pub const Allocator = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        alloc: *const fn (ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8,
        resize: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) ?[*]u8,
        free: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void,
    };
};

Métodos Principais

Alocação

// Aloca um slice de N elementos do tipo T
pub fn alloc(self: Allocator, comptime T: type, n: usize) Allocator.Error![]T

// Aloca e inicializa com zero
pub fn allocWithOptions(self: Allocator, comptime T: type, n: usize, comptime alignment: ?u29, comptime sentinel: ?T) ![]T

// Cria uma única instância na heap
pub fn create(self: Allocator, comptime T: type) Allocator.Error!*T

Liberação

// Libera memória alocada
pub fn free(self: Allocator, memory: anytype) void

// Destrói instância criada com create
pub fn destroy(self: Allocator, ptr: anytype) void

Redimensionamento

// Tenta redimensionar sem realocar
pub fn resize(self: Allocator, old_mem: anytype, new_n: usize) ?[]T

// Realoca (pode mover os dados)
pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) ![]T

Duplicação

// Duplica um slice
pub fn dupe(self: Allocator, comptime T: type, m: []const T) ![]T

// Duplica string com sentinela null
pub fn dupeZ(self: Allocator, comptime T: type, m: []const T) ![:0]T

Exemplo 1: Uso Básico de Alocador

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) {
            std.debug.print("Vazamento de memória detectado!\n", .{});
        }
    }
    const allocator = gpa.allocator();

    // Alocar array dinâmico
    const numeros = try allocator.alloc(u32, 10);
    defer allocator.free(numeros);

    for (numeros, 0..) |*n, i| {
        n.* = @intCast(i * i);
    }

    std.debug.print("Quadrados: ", .{});
    for (numeros) |n| {
        std.debug.print("{d} ", .{n});
    }
    std.debug.print("\n", .{});

    // Criar instância na heap
    const Ponto = struct { x: f64, y: f64 };
    const p = try allocator.create(Ponto);
    defer allocator.destroy(p);

    p.* = .{ .x = 3.14, .y = 2.71 };
    std.debug.print("Ponto: ({d:.2}, {d:.2})\n", .{ p.x, p.y });
}

Exemplo 2: Função que Recebe Allocator

const std = @import("std");
const Allocator = std.mem.Allocator;

const Pessoa = struct {
    nome: []const u8,
    idade: u32,
    allocator: Allocator,

    fn init(allocator: Allocator, nome: []const u8, idade: u32) !Pessoa {
        return .{
            .nome = try allocator.dupe(u8, nome),
            .idade = idade,
            .allocator = allocator,
        };
    }

    fn deinit(self: *Pessoa) void {
        self.allocator.free(self.nome);
    }

    fn apresentar(self: Pessoa) void {
        std.debug.print("Olá, sou {s} e tenho {d} anos.\n", .{ self.nome, self.idade });
    }
};

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

    var pessoa = try Pessoa.init(allocator, "Maria", 30);
    defer pessoa.deinit();

    pessoa.apresentar();
}

Exemplo 3: Construindo String Dinâmica

const std = @import("std");

fn construirSaudacao(allocator: std.mem.Allocator, nomes: []const []const u8) ![]u8 {
    var lista = std.ArrayList(u8).init(allocator);
    defer lista.deinit();

    const writer = lista.writer();

    try writer.writeAll("Olá");
    for (nomes, 0..) |nome, i| {
        if (i == nomes.len - 1 and nomes.len > 1) {
            try writer.writeAll(" e ");
        } else if (i > 0) {
            try writer.writeAll(", ");
        } else {
            try writer.writeAll(" ");
        }
        try writer.writeAll(nome);
    }
    try writer.writeAll("!");

    return try lista.toOwnedSlice();
}

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

    const nomes = [_][]const u8{ "Ana", "Bruno", "Carla" };
    const msg = try construirSaudacao(allocator, &nomes);
    defer allocator.free(msg);

    std.debug.print("{s}\n", .{msg}); // "Olá Ana, Bruno e Carla!"
}

Padrões Comuns

Padrão init/deinit

Tipos que alocam memória seguem o padrão init/deinit:

const MeuTipo = struct {
    dados: []u8,
    allocator: Allocator,

    pub fn init(allocator: Allocator) !MeuTipo {
        return .{
            .dados = try allocator.alloc(u8, 1024),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *MeuTipo) void {
        self.allocator.free(self.dados);
    }
};

Escolhendo o Alocador

// Uso geral com detecção de vazamentos
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();

// Arena para alocações temporárias em grupo
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc_arena = arena.allocator();

// Buffer fixo sem syscalls
var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const alloc_fba = fba.allocator();

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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