Como Usar HashMap em Rust

Aprenda a usar HashMap em Rust: criar, inserir, buscar, usar a Entry API, iterar e contar palavras. Guia completo com exemplos práticos e código executável.

O HashMap é uma das estruturas de dados mais úteis em Rust. Ele armazena pares chave-valor com busca em tempo O(1) amortizado, sendo ideal para caches, contadores, índices e mapeamentos em geral. Nesta receita, você vai aprender a criar, inserir, buscar, atualizar e iterar sobre HashMaps com exemplos completos e executáveis.

Dependências

O HashMap faz parte da biblioteca padrão. Não é necessário adicionar nada ao Cargo.toml:

[package]
name = "receita-hashmap"
version = "0.1.0"
edition = "2021"

Código Completo

use std::collections::HashMap;

fn main() {
    // =============================================
    // 1. Criando um HashMap
    // =============================================
    let mut notas: HashMap<String, f64> = HashMap::new();

    // Criar a partir de um array de tuplas
    let capitais = HashMap::from([
        ("Brasil", "Brasília"),
        ("Argentina", "Buenos Aires"),
        ("Chile", "Santiago"),
    ]);

    // Criar a partir de dois iteradores com zip + collect
    let chaves = vec!["um", "dois", "três"];
    let valores = vec![1, 2, 3];
    let mapa: HashMap<&str, i32> = chaves.into_iter().zip(valores).collect();
    println!("Mapa criado com zip: {:?}", mapa);

    // =============================================
    // 2. Inserindo valores
    // =============================================
    notas.insert("Maria".to_string(), 9.5);
    notas.insert("João".to_string(), 8.0);
    notas.insert("Ana".to_string(), 10.0);
    notas.insert("Carlos".to_string(), 7.5);

    // insert() retorna Option com o valor anterior (se existia)
    let anterior = notas.insert("João".to_string(), 8.5);
    println!("Nota anterior do João: {:?}", anterior); // Some(8.0)

    // =============================================
    // 3. Buscando valores com get()
    // =============================================
    // get() retorna Option<&V>
    if let Some(nota) = notas.get("Maria") {
        println!("Nota da Maria: {}", nota);
    }

    // get() com chave inexistente retorna None
    match notas.get("Pedro") {
        Some(nota) => println!("Nota do Pedro: {}", nota),
        None => println!("Pedro não encontrado"),
    }

    // contains_key() verifica se a chave existe
    println!("Ana está no mapa? {}", notas.contains_key("Ana"));

    // =============================================
    // 4. Entry API — inserir ou atualizar
    // =============================================
    // or_insert: insere valor padrão se a chave não existe
    notas.entry("Pedro".to_string()).or_insert(6.0);
    println!("Nota do Pedro (inserida): {}", notas["Pedro"]);

    // or_insert_with: insere resultado de closure se não existe
    notas.entry("Lucas".to_string()).or_insert_with(|| {
        println!("Calculando nota padrão para Lucas...");
        5.0
    });

    // Atualizar valor existente via Entry API
    notas.entry("Maria".to_string())
        .and_modify(|nota| *nota += 0.5)
        .or_insert(0.0);
    println!("Nota da Maria atualizada: {}", notas["Maria"]);

    // =============================================
    // 5. Iterando sobre o HashMap
    // =============================================
    println!("\n--- Todas as notas ---");
    for (nome, nota) in &notas {
        println!("{}: {:.1}", nome, nota);
    }

    // Iterando apenas sobre chaves
    print!("Alunos: ");
    for nome in notas.keys() {
        print!("{} ", nome);
    }
    println!();

    // Iterando apenas sobre valores
    let media: f64 = notas.values().sum::<f64>() / notas.len() as f64;
    println!("Média da turma: {:.2}", media);

    // =============================================
    // 6. Removendo entradas
    // =============================================
    let removido = notas.remove("Carlos");
    println!("\nRemovido Carlos: {:?}", removido); // Some(7.5)

    // =============================================
    // 7. Exemplo prático: contagem de palavras
    // =============================================
    let texto = "rust é rápido rust é seguro rust é divertido aprender rust";
    let mut contagem: HashMap<&str, usize> = HashMap::new();

    for palavra in texto.split_whitespace() {
        *contagem.entry(palavra).or_insert(0) += 1;
    }

    println!("\n--- Contagem de palavras ---");
    // Ordenar por frequência (maior para menor)
    let mut palavras: Vec<(&&str, &usize)> = contagem.iter().collect();
    palavras.sort_by(|a, b| b.1.cmp(a.1));

    for (palavra, freq) in palavras {
        println!("{}: {}", palavra, freq);
    }

    // =============================================
    // 8. HashMap com tipos customizados como chave
    // =============================================
    #[derive(Debug, PartialEq, Eq, Hash)]
    struct Coordenada {
        x: i32,
        y: i32,
    }

    let mut grid: HashMap<Coordenada, &str> = HashMap::new();
    grid.insert(Coordenada { x: 0, y: 0 }, "origem");
    grid.insert(Coordenada { x: 1, y: 2 }, "ponto A");

    if let Some(local) = grid.get(&Coordenada { x: 0, y: 0 }) {
        println!("\n(0,0) = {}", local);
    }

    // Informações úteis
    println!("\nCapitais: {:?}", capitais);
    println!("Total de alunos: {}", notas.len());
    println!("Mapa vazio? {}", notas.is_empty());
}

Saída do Programa

Mapa criado com zip: {"um": 1, "dois": 2, "três": 3}
Nota anterior do João: Some(8.0)
Nota da Maria: 9.5
Pedro não encontrado
Ana está no mapa? true
Nota do Pedro (inserida): 6.0
Calculando nota padrão para Lucas...
Nota da Maria atualizada: 10.0

--- Todas as notas ---
Maria: 10.0
João: 8.5
Ana: 10.0
Pedro: 6.0
Lucas: 5.0
Alunos: Maria João Ana Pedro Lucas
Média da turma: 7.90

Removido Carlos: Some(7.5)

--- Contagem de palavras ---
rust: 4
é: 3
rápido: 1
seguro: 1
divertido: 1
aprender: 1

(0,0) = origem

Capitais: {"Brasil": "Brasília", "Argentina": "Buenos Aires", "Chile": "Santiago"}
Total de alunos: 5
Mapa vazio? false

Dicas Importantes

  • Chaves devem implementar Eq e Hash: tipos primitivos, String e &str já implementam. Para structs próprias, use #[derive(PartialEq, Eq, Hash)].
  • A Entry API evita buscas duplicadas: em vez de verificar com contains_key e depois inserir, use entry().or_insert() para fazer tudo em uma operação.
  • A ordem de iteração não é garantida: se precisar de ordem, considere usar BTreeMap.
  • Capacidade inicial: use HashMap::with_capacity(n) quando souber o tamanho aproximado para evitar realocações.

Veja Também