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?
| Necessidade | Tipo indicado | Cuidado principal |
|---|---|---|
| Chaves inteiras ou structs compatíveis | std.AutoHashMap(K, V) | Não aceita slices como strings automaticamente |
Chaves de texto []const u8 | std.StringHashMap(V) | Compara conteúdo, mas não copia os bytes da chave |
| Hash e igualdade personalizados | std.HashMap(K, V, Context, carga) | Chaves consideradas iguais precisam produzir o mesmo hash |
| Iteração em armazenamento contíguo | std.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
getOrPutretornafound_existing == false, o valor ainda não foi inicializado. Atribuaentry.value_ptr.*antes de lê-lo ou incrementá-lo. - Não guarde ponteiros após crescimento:
putegetOrPutpodem realocar a tabela. Ponteiros degetPtr, 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
- std.ArrayHashMap — armazenamento contíguo e diferentes estratégias de remoção
- std.mem.Allocator — Interface de alocação
- std.crypto.hash — hashes criptográficos; não confundir com o hash de uma tabela
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
- Como usar HashMap em Zig — receita complementar de pares chave-valor
- Hash map com endereçamento aberto — entenda a implementação de uma tabela hash
- ArenaAllocator — opção para alocações com um tempo de vida compartilhado
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.