Como Usar Arrays Dinâmicos (ArrayList) em Zig

Introdução

Em muitas situações, você precisa de uma coleção que cresce e diminui dinamicamente durante a execução do programa. Arrays fixos em Zig têm tamanho definido em tempo de compilação, o que nem sempre é suficiente. Para isso, a biblioteca padrão oferece std.ArrayList, uma estrutura que gerencia automaticamente a realocação de memória conforme novos elementos são adicionados.

Nesta receita, você aprenderá a criar, manipular e iterar sobre ArrayLists em Zig, cobrindo as operações mais comuns do dia a dia.

Pré-requisitos

O exemplo mais básico: criar um ArrayList, adicionar elementos e iterar sobre eles.

const std = @import("std");

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

    // Criar ArrayList de inteiros
    var lista = std.ArrayList(i32).init(allocator);
    defer lista.deinit();

    // Adicionar elementos
    try lista.append(10);
    try lista.append(20);
    try lista.append(30);
    try lista.append(40);
    try lista.append(50);

    std.debug.print("Tamanho: {d}\n", .{lista.items.len});
    std.debug.print("Capacidade: {d}\n", .{lista.capacity});

    // Iterar sobre os elementos
    std.debug.print("Elementos: ", .{});
    for (lista.items) |item| {
        std.debug.print("{d} ", .{item});
    }
    std.debug.print("\n", .{});
}

Saída esperada

Tamanho: 5
Capacidade: 8
Elementos: 10 20 30 40 50

Adicionar Múltiplos Elementos

Use appendSlice para adicionar vários elementos de uma vez:

const std = @import("std");

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

    var numeros = std.ArrayList(u32).init(allocator);
    defer numeros.deinit();

    // Adicionar slice de elementos
    try numeros.appendSlice(&[_]u32{ 1, 2, 3, 4, 5 });

    // Adicionar mais elementos individualmente
    try numeros.append(6);
    try numeros.append(7);

    // Adicionar outro slice
    const extras = [_]u32{ 8, 9, 10 };
    try numeros.appendSlice(&extras);

    std.debug.print("Total: {d} elementos\n", .{numeros.items.len});
    for (numeros.items) |n| {
        std.debug.print("{d} ", .{n});
    }
    std.debug.print("\n", .{});
}

Inserir e Remover Elementos

Inserir em posições específicas e remover elementos:

const std = @import("std");

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

    var lista = std.ArrayList([]const u8).init(allocator);
    defer lista.deinit();

    try lista.append("Banana");
    try lista.append("Maçã");
    try lista.append("Uva");

    std.debug.print("Original: ", .{});
    for (lista.items) |item| {
        std.debug.print("{s} ", .{item});
    }
    std.debug.print("\n", .{});

    // Inserir na posição 1
    try lista.insert(1, "Laranja");

    std.debug.print("Após inserir Laranja na posição 1: ", .{});
    for (lista.items) |item| {
        std.debug.print("{s} ", .{item});
    }
    std.debug.print("\n", .{});

    // Remover por índice (orderedRemove mantém a ordem)
    _ = lista.orderedRemove(0);

    std.debug.print("Após remover posição 0: ", .{});
    for (lista.items) |item| {
        std.debug.print("{s} ", .{item});
    }
    std.debug.print("\n", .{});

    // swapRemove é O(1) mas não mantém a ordem
    _ = lista.swapRemove(0);

    std.debug.print("Após swapRemove posição 0: ", .{});
    for (lista.items) |item| {
        std.debug.print("{s} ", .{item});
    }
    std.debug.print("\n", .{});
}

Pré-alocar Capacidade

Se você sabe quantos elementos vai precisar, pré-aloque para evitar realocações:

const std = @import("std");

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

    var lista = std.ArrayList(f64).init(allocator);
    defer lista.deinit();

    // Pré-alocar espaço para 1000 elementos
    try lista.ensureTotalCapacity(1000);

    std.debug.print("Capacidade inicial: {d}\n", .{lista.capacity});

    // Adicionar 1000 elementos sem realocação
    for (0..1000) |i| {
        try lista.append(@as(f64, @floatFromInt(i)) * 1.5);
    }

    std.debug.print("Tamanho final: {d}\n", .{lista.items.len});
    std.debug.print("Capacidade final: {d}\n", .{lista.capacity});
}

Converter entre ArrayList e Slice

const std = @import("std");

pub fn somaSlice(nums: []const i32) i64 {
    var soma: i64 = 0;
    for (nums) |n| {
        soma += n;
    }
    return soma;
}

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

    var lista = std.ArrayList(i32).init(allocator);
    defer lista.deinit();

    try lista.appendSlice(&[_]i32{ 10, 20, 30, 40, 50 });

    // ArrayList.items é um slice — use diretamente
    const resultado = somaSlice(lista.items);
    std.debug.print("Soma: {d}\n", .{resultado});

    // toOwnedSlice transfere a propriedade da memória
    const owned = try lista.toOwnedSlice();
    defer allocator.free(owned);

    std.debug.print("Owned slice tamanho: {d}\n", .{owned.len});
    // Após toOwnedSlice, a lista fica vazia
    std.debug.print("Lista após toOwnedSlice: {d} elementos\n", .{lista.items.len});
}

Exemplo Prático: Coletar Números Pares

const std = @import("std");

fn coletarPares(allocator: std.mem.Allocator, numeros: []const i32) ![]i32 {
    var pares = std.ArrayList(i32).init(allocator);
    errdefer pares.deinit();

    for (numeros) |n| {
        if (@mod(n, 2) == 0) {
            try pares.append(n);
        }
    }

    return pares.toOwnedSlice();
}

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

    const dados = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

    const pares = try coletarPares(allocator, &dados);
    defer allocator.free(pares);

    std.debug.print("Números pares: ", .{});
    for (pares) |p| {
        std.debug.print("{d} ", .{p});
    }
    std.debug.print("\n", .{});
    std.debug.print("Total: {d}\n", .{pares.len});
}

Saída esperada

Números pares: 2 4 6 8 10 12
Total: 6

Dicas e Boas Práticas

  1. Sempre libere a memória: Use defer lista.deinit() logo após criar o ArrayList.

  2. Pré-aloque quando possível: Se souber o tamanho aproximado, use ensureTotalCapacity para evitar realocações.

  3. Use toOwnedSlice com cuidado: Após chamar, a lista fica vazia e a responsabilidade de liberar a memória passa para você.

  4. Prefira orderedRemove quando a ordem importa: swapRemove é mais rápido (O(1)) mas altera a ordem dos elementos.

  5. Use errdefer: Ao construir um ArrayList em uma função que pode falhar, use errdefer lista.deinit() para evitar vazamentos.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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