HashMap em Zig (2026): AutoHashMap, StringHashMap e getOrPut

HashMap em Zig: AutoHashMap, StringHashMap e getOrPut

Resposta direta: para criar um dicionário em Zig, use std.AutoHashMap(K, V) com chaves numéricas ou structs compatíveis e std.StringHashMap(V) com chaves []const u8. Insira com try mapa.put(chave, valor), consulte com mapa.get(chave) e libere a tabela com defer mapa.deinit(). Use std.HashMap diretamente quando precisar definir o contexto de hash e igualdade.

A tabela usa endereçamento aberto e não garante ordem de iteração. Não existe uma vantagem universal de desempenho sobre ArrayHashMap: escolha conforme o padrão de acesso e meça no seu programa.

Qual HashMap escolher?

NecessidadeTipo indicadoCuidado principal
Chaves inteiras ou structs compatíveisstd.AutoHashMap(K, V)Não aceita slices como strings automaticamente
Chaves de texto []const u8std.StringHashMap(V)Compara conteúdo, mas não copia os bytes da chave
Hash e igualdade personalizadosstd.HashMap(K, V, Context, carga)Chaves consideradas iguais precisam produzir o mesmo hash
Iteração em armazenamento contíguostd.AutoArrayHashMap(K, V) ou std.StringArrayHashMap(V)A remoção por troca pode alterar a ordem

Os três programas completos abaixo foram executados com Zig 0.14.1 — a versão estável atual do Zig é a 0.17.0, lançada em outubro de 2026, então confira zig version antes de adaptar um exemplo: APIs da biblioteca padrão mudam entre versões. Usam std.debug.print, que escreve em stderr, para evitar dependência das APIs de stdout. Para saída de uma ferramenta de linha de comando, consulte o guia de migração de I/O.

Visão Geral

const std = @import("std");
const HashMap = std.HashMap;

Tipo e Assinatura

pub fn HashMap(
    comptime K: type,
    comptime V: type,
    comptime Context: type,
    comptime max_load_percentage: u64,
) type

Para uso simplificado, utilize std.AutoHashMap que configura o contexto e o fator de carga automaticamente:

pub fn AutoHashMap(comptime K: type, comptime V: type) type

O AutoHashMap usa a função de hash padrão do Zig, que é uma implementação otimizada de Wyhash.

Funções Principais

Criação e Destruição

// Cria um mapa vazio
pub fn init(allocator: Allocator) AutoHashMap(K, V)

// Libera a memória interna da tabela, não as alocações das chaves/valores
pub fn deinit(self: *Self) void

Inserção e Acesso

// Insere ou atualiza
pub fn put(self: *Self, key: K, value: V) Allocator.Error!void

// Busca valor pela chave
pub fn get(self: Self, key: K) ?V

// Busca ponteiro para o valor
pub fn getPtr(self: Self, key: K) ?*V

// Insere se não existe, retorna entrada existente ou nova
pub fn getOrPut(self: *Self, key: K) Allocator.Error!GetOrPutResult

// Verifica existência
pub fn contains(self: Self, key: K) bool

// Número de elementos
pub fn count(self: Self) Size

Remoção

// Remove a chave e retorna o valor, ou null
pub fn fetchRemove(self: *Self, key: K) ?KV

// Remove a chave (retorna bool)
pub fn remove(self: *Self, key: K) bool

Iteração

// Retorna um iterador sobre entradas
pub fn iterator(self: *const Self) Iterator

// Retorna iterador sobre chaves
pub fn keyIterator(self: Self) KeyIterator

// Retorna iterador sobre valores
pub fn valueIterator(self: Self) ValueIterator

Exemplo 1: Cache de Resultados com HashMap

const std = @import("std");

fn fibonacci(n: u64, cache: *std.AutoHashMap(u64, u64)) !u64 {
    if (n <= 1) return n;

    if (cache.get(n)) |resultado| {
        return resultado;
    }

    const resultado = try fibonacci(n - 1, cache) + try fibonacci(n - 2, cache);
    try cache.put(n, resultado);
    return resultado;
}

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

    var cache = std.AutoHashMap(u64, u64).init(allocator);
    defer cache.deinit();

    const valores = [_]u64{ 10, 20, 30, 40, 50 };

    for (valores) |n| {
        const resultado = try fibonacci(n, &cache);
        std.debug.print("fibonacci({d}) = {d}\n", .{ n, resultado });
    }

    std.debug.print("Entradas no cache: {d}\n", .{cache.count()});
}

Exemplo 2: Contagem de Caracteres com getOrPut

const std = @import("std");

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

    // Mapa de byte para contagem
    var frequencia = std.AutoHashMap(u8, u32).init(allocator);
    defer frequencia.deinit();

    const texto = "abracadabra";

    for (texto) |c| {
        const entry = try frequencia.getOrPut(c);
        if (entry.found_existing) {
            entry.value_ptr.* += 1;
        } else {
            entry.value_ptr.* = 1;
        }
    }

    std.debug.print("Frequência de caracteres em '{s}':\n", .{texto});

    var it = frequencia.iterator();
    while (it.next()) |entry| {
        std.debug.print("  '{c}': {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }
}

Exemplo 3: Agrupamento com getOrPut

const std = @import("std");

const Pessoa = struct {
    nome: []const u8,
    cidade: []const u8,
    idade: u32,
};

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

    const pessoas = [_]Pessoa{
        .{ .nome = "Ana", .cidade = "SP", .idade = 25 },
        .{ .nome = "Bruno", .cidade = "RJ", .idade = 30 },
        .{ .nome = "Carla", .cidade = "SP", .idade = 28 },
        .{ .nome = "Diego", .cidade = "RJ", .idade = 35 },
        .{ .nome = "Eva", .cidade = "MG", .idade = 22 },
    };

    // Agrupa contagem por cidade
    var por_cidade = std.StringHashMap(u32).init(allocator);
    defer por_cidade.deinit();

    for (pessoas) |p| {
        const entry = try por_cidade.getOrPut(p.cidade);
        if (entry.found_existing) {
            entry.value_ptr.* += 1;
        } else {
            entry.value_ptr.* = 1;
        }
    }

    std.debug.print("Pessoas por cidade:\n", .{});
    var it = por_cidade.iterator();
    while (it.next()) |entry| {
        std.debug.print("  {s}: {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }
}

As chaves acima apontam para literais de string, cujo tempo de vida cobre todo o programa. Para chaves vindas de um buffer reutilizado, copie os bytes ou mantenha o buffer vivo: StringHashMap não assume a propriedade deles.

Memória, getOrPut e Ponteiros: Armadilhas Comuns

  • Inicialize uma entrada nova: quando getOrPut retorna found_existing == false, o valor ainda não foi inicializado. Atribua entry.value_ptr.* antes de lê-lo ou incrementá-lo.
  • Não guarde ponteiros após crescimento: put e getOrPut podem realocar a tabela. Ponteiros de getPtr, entradas e iteradores podem ser invalidados; obtenha-os novamente depois de uma mutação estrutural.
  • Não altere a tabela durante a iteração: para remover entradas, primeiro reúna as chaves a remover ou use outra estratégia que não dependa do iterador invalidado.

O padrão seguro de remoção durante a iteração é coletar as chaves primeiro e removê-las depois, com o iterador já encerrado:

const std = @import("std");

// mapa: std.AutoHashMap(u32, i64) já preenchido
var para_remover = std.ArrayList(u32).init(allocator);
defer para_remover.deinit();

var it = mapa.iterator();
while (it.next()) |entry| {
    if (entry.value_ptr.* <= 0) {
        try para_remover.append(entry.key_ptr.*);
    }
}

for (para_remover.items) |k| {
    _ = mapa.remove(k);
}
  • Libere as alocações que você criou: deinit() libera apenas o armazenamento da tabela. Se você duplicou strings ou alocou valores, libere-os separadamente, antes de destruir o mapa.
  • Não altere os bytes de uma chave inserida: isso pode mudar seu hash e impedir que a busca encontre a entrada.
  • Reserve capacidade quando souber o tamanho: try mapa.ensureTotalCapacity(n) pode evitar realocações repetidas; não transforma qualquer mutação em uma operação segura para iteradores.

No exemplo de frequência, a contagem é de bytes, não de caracteres Unicode. Com "abracadabra", a aparece 5 vezes e b aparece 2 vezes; para texto UTF-8, decodifique os pontos de código antes de contar.

Padrões Comuns

Hash Customizado

Para tipos personalizados como chaves, implemente um contexto com hash e eql:

const Ponto = struct { x: i32, y: i32 };

const PontoContext = struct {
    pub fn hash(self: @This(), p: Ponto) u64 {
        _ = self;
        var h = std.hash.Wyhash.init(0);
        h.update(std.mem.asBytes(&p.x));
        h.update(std.mem.asBytes(&p.y));
        return h.final();
    }
    pub fn eql(self: @This(), a: Ponto, b: Ponto) bool {
        _ = self;
        return a.x == b.x and a.y == b.y;
    }
};

var mapa = std.HashMap(Ponto, []const u8, PontoContext, 80).init(allocator);

Módulos Relacionados

Perguntas Frequentes

Por que AutoHashMap([]const u8, V) não compila?

O hash automático não escolhe uma semântica para slices. Para comparar strings pelo conteúdo, use std.StringHashMap(V); para outra semântica, implemente um contexto próprio.

get retorna um valor ou um ponteiro?

get(chave) retorna uma cópia opcional do valor (?V). Para modificar o valor armazenado, use getPtr(chave) ou getOrPut(chave) e respeite as regras de invalidação dos ponteiros.

put substitui um valor existente?

Sim. put insere ou atualiza o valor. Se o valor anterior contém uma alocação de sua responsabilidade, libere-a antes de substituí-lo: a tabela não faz essa limpeza por você.

HashMap preserva a ordem de inserção?

Não. Não dependa da ordem de seus iteradores. Para armazenamento contíguo, veja ArrayHashMap; quando a ordem precisar sobreviver às remoções, escolha orderedRemove em vez de swapRemove.

Tutoriais e Receitas Relacionados

Referência Oficial

Consulte o código de std.HashMap no Zig 0.17.0 para as assinaturas completas e regras de invalidação. As APIs da biblioteca padrão variam entre versões: confira a versão com zig version antes de adaptar um exemplo.

Continue aprendendo Zig

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