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étodo | Descrição | Exemplo |
|---|---|---|
insert(k, v) | Insere ou sobrescreve; retorna valor anterior | m.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 existe | m.contains_key("a") |
remove(&k) | Remove e retorna Option<V> | m.remove("a") |
entry(k) | Retorna uma Entry para manipulação condicional | m.entry("a").or_insert(0) |
len() | Número de pares | m.len() |
is_empty() | Verifica se está vazio | m.is_empty() |
keys() | Iterador sobre as chaves | m.keys() |
values() | Iterador sobre os valores | m.values() |
values_mut() | Iterador mutável sobre os valores | m.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 pares | m.drain() |
retain(f) | Mantém pares onde f retorna true | m.retain(|k, v| *v > 0) |
extend(iter) | Insere pares de um iterador | m.extend([("b", 2)]) |
clear() | Remove todos os elementos | m.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
Ordem não garantida: A iteração sobre um
HashMapnão segue nenhuma ordem específica. Se precisar de ordem, useBTreeMap(ordenado por chave) ou colete em umVece ordene.Use
with_capacity: Se souber o número aproximado de entradas,HashMap::with_capacity(n)evita rehashing, que é uma operação custosa.Prefira
entry()aget()+insert(): A Entry API evita buscas duplicadas na tabela hash. Em vez de verificar comcontains_keye depois inserir, useentry().or_insert().Chaves
Stringvs&str: Se as chaves são literais conhecidos em compilação, use&strpara evitar alocações. Se são dinâmicos, useString.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 comoahashourustc-hashpara melhor performance.Cuidado com
[]para acesso:mapa["chave"]causa panic se a chave não existir. Prefiramapa.get("chave")que retornaOption<&V>.
Veja Também
- Usar HashMap em Rust — receitas práticas com HashMap
- Vec<T>: Vetor Dinâmico — para coleções ordenadas por índice
- String e &str — frequentemente usado como chave de HashMap
- Iterator Trait — para transformar e processar pares chave-valor
- Option<T> — retornado por
get()e outros métodos - Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::collections::HashMap