HashMap<K,V> em Rust

Guia completo de HashMap em Rust: criação, inserção, busca, entry API, iteração, contagem de ocorrências e padrões práticos em português.

O que é HashMap<K,V>

O HashMap<K,V> é uma coleção de pares chave-valor onde cada chave é única. Internamente, ele usa uma tabela hash para oferecer inserção, busca e remoção em tempo O(1) amortizado. As chaves devem implementar os traits Eq e Hash — tipos como String, &str, inteiros e tuplas desses tipos funcionam nativamente.

Use HashMap quando precisar associar dados a identificadores únicos: contar ocorrências de palavras, cachear resultados, mapear IDs a registros, ou qualquer situação onde o acesso rápido por chave é mais importante que a ordem dos elementos. Se precisar de ordem de inserção, considere IndexMap (crate externo) ou BTreeMap (que ordena por chave).


Criando HashMaps

use std::collections::HashMap;

fn main() {
    // HashMap vazio
    let mut mapa: HashMap<String, i32> = HashMap::new();
    mapa.insert("um".to_string(), 1);

    // Com capacidade pré-alocada
    let mut mapa2: HashMap<&str, i32> = HashMap::with_capacity(100);
    mapa2.insert("dois", 2);

    // A partir de tuplas com collect
    let mapa3: HashMap<&str, i32> = vec![
        ("um", 1),
        ("dois", 2),
        ("três", 3),
    ].into_iter().collect();

    // A partir de arrays de tuplas (Rust 1.56+)
    let mapa4 = HashMap::from([
        ("vermelho", "#FF0000"),
        ("verde", "#00FF00"),
        ("azul", "#0000FF"),
    ]);

    // A partir de dois vetores com zip
    let chaves = vec!["a", "b", "c"];
    let valores = vec![1, 2, 3];
    let mapa5: HashMap<&str, i32> = chaves.into_iter().zip(valores).collect();

    println!("{mapa3:?}");
    println!("{mapa4:?}");
    println!("{mapa5:?}");
}

Tabela de Métodos Principais

MétodoDescriçãoExemplo
insert(k, v)Insere ou sobrescreve; retorna valor anteriorm.insert("a", 1)
get(&k)Retorna Option<&V>m.get("a")Some(&1)
get_mut(&k)Retorna Option<&mut V>m.get_mut("a")
contains_key(&k)Verifica se a chave existem.contains_key("a")
remove(&k)Remove e retorna Option<V>m.remove("a")
entry(k)Retorna uma Entry para manipulação condicionalm.entry("a").or_insert(0)
len()Número de paresm.len()
is_empty()Verifica se está vaziom.is_empty()
keys()Iterador sobre as chavesm.keys()
values()Iterador sobre os valoresm.values()
values_mut()Iterador mutável sobre os valoresm.values_mut()
iter()Iterador de (&K, &V)m.iter()
iter_mut()Iterador de (&K, &mut V)m.iter_mut()
drain()Remove e itera sobre todos os paresm.drain()
retain(f)Mantém pares onde f retorna truem.retain(|k, v| *v > 0)
extend(iter)Insere pares de um iteradorm.extend([("b", 2)])
clear()Remove todos os elementosm.clear()

Exemplos Práticos

1. A Entry API: Inserção Condicional

A Entry API é um dos recursos mais poderosos do HashMap. Ela permite verificar se uma chave existe e agir de acordo, tudo em uma única operação eficiente.

use std::collections::HashMap;

fn main() {
    let mut estoque: HashMap<&str, u32> = HashMap::new();

    // or_insert: insere o valor se a chave não existir
    estoque.entry("maçã").or_insert(0);
    estoque.entry("banana").or_insert(10);

    // or_insert_with: insere usando uma closure (avaliação preguiçosa)
    estoque.entry("laranja").or_insert_with(|| {
        println!("Calculando estoque inicial...");
        calcular_estoque_padrao()
    });

    // and_modify + or_insert: modifica se existir, insere se não
    let frutas = vec!["maçã", "banana", "maçã", "maçã", "banana", "laranja"];
    for fruta in frutas {
        estoque.entry(fruta)
            .and_modify(|qtd| *qtd += 1)
            .or_insert(1);
    }

    for (fruta, qtd) in &estoque {
        println!("{fruta}: {qtd}");
    }
}

fn calcular_estoque_padrao() -> u32 {
    5
}

2. Contando Ocorrências de Palavras

use std::collections::HashMap;

fn contar_palavras(texto: &str) -> HashMap<String, usize> {
    let mut contagem = HashMap::new();

    for palavra in texto.split_whitespace() {
        let normalizada = palavra
            .to_lowercase()
            .trim_matches(|c: char| !c.is_alphanumeric())
            .to_string();

        if !normalizada.is_empty() {
            *contagem.entry(normalizada).or_insert(0) += 1;
        }
    }

    contagem
}

fn main() {
    let texto = "Rust é rápido. Rust é seguro. Rust é produtivo!";
    let contagem = contar_palavras(texto);

    // Ordenar por frequência (decrescente)
    let mut pares: Vec<_> = contagem.iter().collect();
    pares.sort_by(|a, b| b.1.cmp(a.1));

    for (palavra, qtd) in pares {
        println!("{palavra}: {qtd}");
    }
}

3. Agrupando Dados por Categoria

use std::collections::HashMap;

#[derive(Debug)]
struct Produto {
    nome: String,
    categoria: String,
    preco: f64,
}

fn main() {
    let produtos = vec![
        Produto { nome: "Arroz".into(), categoria: "Alimentos".into(), preco: 5.99 },
        Produto { nome: "Feijão".into(), categoria: "Alimentos".into(), preco: 8.49 },
        Produto { nome: "Sabão".into(), categoria: "Limpeza".into(), preco: 3.29 },
        Produto { nome: "Detergente".into(), categoria: "Limpeza".into(), preco: 2.19 },
        Produto { nome: "Café".into(), categoria: "Alimentos".into(), preco: 12.90 },
    ];

    // Agrupar por categoria
    let mut por_categoria: HashMap<&str, Vec<&Produto>> = HashMap::new();
    for produto in &produtos {
        por_categoria
            .entry(&produto.categoria)
            .or_insert_with(Vec::new)
            .push(produto);
    }

    // Exibir agrupamentos com total
    for (categoria, lista) in &por_categoria {
        let total: f64 = lista.iter().map(|p| p.preco).sum();
        println!("\n{categoria} (total: R$ {total:.2}):");
        for p in lista {
            println!("  - {} (R$ {:.2})", p.nome, p.preco);
        }
    }
}

4. Cache Simples (Memoização)

use std::collections::HashMap;

fn fibonacci_memo(n: u64, cache: &mut HashMap<u64, u64>) -> u64 {
    if n <= 1 {
        return n;
    }

    if let Some(&resultado) = cache.get(&n) {
        return resultado;
    }

    let resultado = fibonacci_memo(n - 1, cache) + fibonacci_memo(n - 2, cache);
    cache.insert(n, resultado);
    resultado
}

fn main() {
    let mut cache = HashMap::new();

    for n in [10, 20, 30, 40, 50] {
        let resultado = fibonacci_memo(n, &mut cache);
        println!("fib({n}) = {resultado}");
    }

    println!("Entradas no cache: {}", cache.len());
}

5. Mesclando Dois HashMaps

use std::collections::HashMap;

fn mesclar(
    base: &mut HashMap<String, i32>,
    outro: HashMap<String, i32>,
    conflito: impl Fn(i32, i32) -> i32,
) {
    for (chave, valor_novo) in outro {
        base.entry(chave)
            .and_modify(|existente| *existente = conflito(*existente, valor_novo))
            .or_insert(valor_novo);
    }
}

fn main() {
    let mut vendas_jan = HashMap::from([
        ("Ana".into(), 150),
        ("Bruno".into(), 200),
        ("Carlos".into(), 100),
    ]);

    let vendas_fev = HashMap::from([
        ("Ana".into(), 180),
        ("Bruno".into(), 160),
        ("Diana".into(), 220),
    ]);

    // Somar vendas dos dois meses
    mesclar(&mut vendas_jan, vendas_fev, |a, b| a + b);

    for (nome, total) in &vendas_jan {
        println!("{nome}: {total}");
    }
    // Ana: 330, Bruno: 360, Carlos: 100, Diana: 220
}

Dicas de Performance e Armadilhas

  1. Ordem não garantida: A iteração sobre um HashMap não segue nenhuma ordem específica. Se precisar de ordem, use BTreeMap (ordenado por chave) ou colete em um Vec e ordene.

  2. Use with_capacity: Se souber o número aproximado de entradas, HashMap::with_capacity(n) evita rehashing, que é uma operação custosa.

  3. Prefira entry() a get() + insert(): A Entry API evita buscas duplicadas na tabela hash. Em vez de verificar com contains_key e depois inserir, use entry().or_insert().

  4. Chaves String vs &str: Se as chaves são literais conhecidos em compilação, use &str para evitar alocações. Se são dinâmicos, use String.

  5. Hasher personalizado: O hasher padrão (SipHash) é resistente a ataques DoS mas não é o mais rápido. Para dados não controlados por usuários, considere crates como ahash ou rustc-hash para melhor performance.

  6. Cuidado com [] para acesso: mapa["chave"] causa panic se a chave não existir. Prefira mapa.get("chave") que retorna Option<&V>.


Veja Também