Como Usar FixedBufferAllocator em Zig

Introdução

O FixedBufferAllocator permite alocar memória a partir de um buffer pré-existente, sem recorrer ao heap. Isso é particularmente útil em sistemas embarcados, código com restrições de tempo real, ou quando você quer garantir que nenhuma alocação de heap aconteça.

Nesta receita, você aprenderá a usar o FixedBufferAllocator para diversas situações práticas.

Pré-requisitos

Uso Básico

Alocar memória a partir de um buffer na stack:

const std = @import("std");

pub fn main() !void {
    // Buffer fixo na stack
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    // Alocar como qualquer outro alocador
    const dados = try allocator.alloc(u8, 100);
    @memset(dados, 'Z');

    const numeros = try allocator.alloc(i32, 10);
    for (numeros, 0..) |*n, i| {
        n.* = @intCast(i * i);
    }

    std.debug.print("String: {s}\n", .{dados[0..3]});
    std.debug.print("Números: ", .{});
    for (numeros) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});

    // Verificar espaço restante
    std.debug.print("Buffer total: {d} bytes\n", .{buffer.len});
    std.debug.print("Posição atual: {d} bytes usados\n", .{fba.end_index});
}

Formatar Strings sem Heap

Use FixedBufferAllocator para formatar strings sem alocação dinâmica:

const std = @import("std");

pub fn main() !void {
    var buffer: [256]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    const nome = "Maria";
    const idade: u32 = 28;

    const mensagem = try std.fmt.allocPrint(
        allocator,
        "Olá, {s}! Você tem {d} anos.",
        .{ nome, idade },
    );

    std.debug.print("{s}\n", .{mensagem});
    std.debug.print("Usados {d} de {d} bytes\n", .{ mensagem.len, buffer.len });

    // Reset para reutilizar o buffer
    fba.reset();

    const outra = try std.fmt.allocPrint(
        allocator,
        "Temperatura: {d:.1}°C",
        .{23.5},
    );
    std.debug.print("{s}\n", .{outra});
}

Tratar Falta de Espaço

const std = @import("std");

pub fn main() !void {
    // Buffer pequeno para demonstrar o limite
    var buffer: [64]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    // Primeira alocação: sucesso
    const a = try allocator.alloc(u8, 30);
    std.debug.print("Alocação 1: {d} bytes (OK)\n", .{a.len});

    // Segunda alocação: sucesso
    const b = try allocator.alloc(u8, 20);
    std.debug.print("Alocação 2: {d} bytes (OK)\n", .{b.len});

    // Terceira alocação: sem espaço
    const c = allocator.alloc(u8, 30);
    if (c) |dados| {
        std.debug.print("Alocação 3: {d} bytes (OK)\n", .{dados.len});
    } else |err| {
        std.debug.print("Alocação 3: falhou com {}\n", .{err});
    }
}

FixedBufferAllocator com ArrayList

Usar estruturas padrão sem heap:

const std = @import("std");

pub fn main() !void {
    var buffer: [4096]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    // ArrayList usando buffer fixo
    var lista = std.ArrayList(i32).init(allocator);
    // Não precisa de defer deinit -- o buffer é na stack

    for (0..20) |i| {
        lista.append(@intCast(i * 3)) catch |err| {
            std.debug.print("Parou no elemento {d}: {}\n", .{ i, err });
            break;
        };
    }

    std.debug.print("Elementos na lista: {d}\n", .{lista.items.len});
    std.debug.print("Lista: ", .{});
    for (lista.items) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});
}

Padrão: Tentar Stack, Fallback para Heap

const std = @import("std");

fn processarDados(allocator: std.mem.Allocator, tamanho: usize) ![]u8 {
    const dados = try allocator.alloc(u8, tamanho);
    for (dados, 0..) |*b, i| {
        b.* = @intCast(i % 256);
    }
    return dados;
}

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

    // Tentar com buffer fixo para dados pequenos
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);

    const tamanho: usize = 500;

    if (processarDados(fba.allocator(), tamanho)) |dados| {
        std.debug.print("Processado na stack: {d} bytes\n", .{dados.len});
    } else |_| {
        // Fallback para heap se o buffer fixo não for suficiente
        const dados = try processarDados(gpa.allocator(), tamanho);
        defer gpa.allocator().free(dados);
        std.debug.print("Processado no heap: {d} bytes\n", .{dados.len});
    }
}

Dicas e Boas Práticas

  1. Tamanho do buffer: Considere o alinhamento de memória. O FBA alinha alocações, então o espaço útil pode ser menor que o tamanho do buffer.

  2. Use reset() para reutilizar: Após processar, faça fba.reset() para reaproveitar o buffer sem realocar.

  3. Free não libera no meio: O FixedBufferAllocator não suporta liberação individual eficiente. Use reset() para liberar tudo.

  4. Ideal para dados temporários: Perfeito quando os dados não precisam sobreviver além do escopo da função.

  5. Combine com ArenaAllocator: ArenaAllocator.init(fba.allocator()) dá o melhor dos dois mundos.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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