HashSet<T>: Conjunto em Rust

Guia completo do HashSet em Rust: conjunto de elementos únicos, operações de união, interseção, diferença, subconjunto e exemplos práticos em português.

O HashSet<T> é a implementação de conjunto (set) da biblioteca padrão do Rust. Ele armazena elementos únicos sem repetição e oferece busca, inserção e remoção em tempo O(1) amortizado. Internamente, é construído sobre um HashMap<T, ()>, aproveitando toda a eficiência da tabela hash.

Quando Usar HashSet

Use HashSet quando:

  • Precisa garantir que não há duplicatas em uma coleção.
  • Precisa verificar pertinência rapidamente (“este elemento está no conjunto?”).
  • Quer realizar operações de conjunto: união, interseção, diferença e diferença simétrica.
  • A ordem dos elementos não importa.

Se precisar de iteração em ordem, use BTreeSet. Se precisar associar valores às chaves, use HashMap.

Criando e Inicializando

use std::collections::HashSet;

fn main() {
    // Criar vazio
    let mut frutas: HashSet<String> = HashSet::new();

    // Criar com capacidade inicial (evita realocações)
    let mut numeros: HashSet<i32> = HashSet::with_capacity(100);

    // Criar a partir de um array
    let cores = HashSet::from(["vermelho", "verde", "azul"]);

    // Criar a partir de um vetor com collect (remove duplicatas!)
    let dados = vec![1, 2, 3, 2, 1, 4, 3, 5];
    let unicos: HashSet<i32> = dados.into_iter().collect();
    println!("Únicos: {:?}", unicos); // {1, 2, 3, 4, 5}

    // Criar a partir de uma string (caracteres únicos)
    let texto = "abracadabra";
    let letras: HashSet<char> = texto.chars().collect();
    println!("Letras únicas: {:?}", letras); // {'a', 'b', 'r', 'c', 'd'}
}

Tabela de Métodos Comuns

MétodoDescriçãoComplexidade
insert(value)Insere elemento; retorna true se era novoO(1) amortizado
remove(&value)Remove elemento; retorna true se existiaO(1) amortizado
contains(&value)Verifica se o elemento existeO(1) amortizado
get(&value)Retorna referência ao elemento, se existirO(1) amortizado
len() / is_empty()Tamanho e verificação de vazioO(1)
clear()Remove todos os elementosO(n)
iter()Iterador sobre referências &TO(n)
union(&other)Elementos em self OU otherO(n + m)
intersection(&other)Elementos em self E otherO(min(n, m))
difference(&other)Elementos em self mas NÃO em otherO(n)
symmetric_difference(&other)Elementos em self OU other, mas não em ambosO(n + m)
is_subset(&other)Verifica se self é subconjunto de otherO(n)
is_superset(&other)Verifica se self é superconjunto de otherO(m)
is_disjoint(&other)Verifica se não há elementos em comumO(min(n, m))
retain(f)Mantém apenas elementos onde f retorna trueO(n)
drain()Remove e retorna todos os elementosO(n)

Exemplos Práticos

1. Inserção, Remoção e Verificação

use std::collections::HashSet;

fn main() {
    let mut linguagens: HashSet<&str> = HashSet::new();

    // insert retorna true se o elemento era novo
    assert!(linguagens.insert("Rust"));
    assert!(linguagens.insert("Python"));
    assert!(linguagens.insert("Go"));
    assert!(!linguagens.insert("Rust")); // já existia!

    println!("Total: {} linguagens", linguagens.len()); // 3

    // Verificação de pertinência
    if linguagens.contains("Rust") {
        println!("Rust está no conjunto!");
    }

    // Remoção
    let removido = linguagens.remove("Go");
    println!("Go removido? {}", removido); // true

    // Iteração (ordem não garantida)
    for lang in &linguagens {
        println!("- {}", lang);
    }
}

2. Operações de Conjunto — União, Interseção, Diferença

use std::collections::HashSet;

fn main() {
    let frontend: HashSet<&str> = HashSet::from([
        "JavaScript", "TypeScript", "HTML", "CSS", "Rust",
    ]);

    let backend: HashSet<&str> = HashSet::from([
        "Rust", "Go", "Python", "Java", "TypeScript",
    ]);

    // União: linguagens em frontend OU backend
    let todas: HashSet<&str> = frontend.union(&backend).copied().collect();
    println!("Todas: {:?}", todas);

    // Interseção: linguagens em AMBOS
    let fullstack: HashSet<&str> = frontend.intersection(&backend).copied().collect();
    println!("Fullstack: {:?}", fullstack); // {"Rust", "TypeScript"}

    // Diferença: apenas frontend (não estão em backend)
    let so_frontend: HashSet<&str> = frontend.difference(&backend).copied().collect();
    println!("Só frontend: {:?}", so_frontend); // {"JavaScript", "HTML", "CSS"}

    // Diferença simétrica: em um OU outro, mas não em ambos
    let exclusivas: HashSet<&str> = frontend.symmetric_difference(&backend).copied().collect();
    println!("Exclusivas: {:?}", exclusivas);

    // Usando operadores (requer coletar em novos HashSets)
    let uniao = &frontend | &backend;       // união
    let inter = &frontend & &backend;       // interseção
    let diff = &frontend - &backend;        // diferença
    let sym_diff = &frontend ^ &backend;    // diferença simétrica

    println!("\nCom operadores:");
    println!("  |  união: {:?}", uniao);
    println!("  &  interseção: {:?}", inter);
    println!("  -  diferença: {:?}", diff);
    println!("  ^  simétrica: {:?}", sym_diff);
}

3. Subconjuntos e Disjunção

use std::collections::HashSet;

fn main() {
    let animais: HashSet<&str> = HashSet::from([
        "gato", "cachorro", "pássaro", "peixe", "hamster",
    ]);

    let domesticos: HashSet<&str> = HashSet::from([
        "gato", "cachorro",
    ]);

    let selvagens: HashSet<&str> = HashSet::from([
        "leão", "tigre", "elefante",
    ]);

    // Subconjunto
    println!("domésticos ⊆ animais? {}", domesticos.is_subset(&animais));
    // true

    // Superconjunto
    println!("animais ⊇ domésticos? {}", animais.is_superset(&domesticos));
    // true

    // Disjuntos (sem elementos em comum)
    println!("domésticos ∩ selvagens = ∅? {}", domesticos.is_disjoint(&selvagens));
    // true

    println!("animais ∩ domésticos = ∅? {}", animais.is_disjoint(&domesticos));
    // false
}

4. Removendo Duplicatas de um Vetor

use std::collections::HashSet;

fn main() {
    let emails = vec![
        "ana@email.com",
        "bruno@email.com",
        "ana@email.com",     // duplicata
        "carla@email.com",
        "bruno@email.com",   // duplicata
        "diana@email.com",
    ];

    // Abordagem 1: converter para HashSet (perde a ordem)
    let unicos: HashSet<&str> = emails.iter().copied().collect();
    println!("Únicos (sem ordem): {:?}", unicos);

    // Abordagem 2: manter a ordem original (usar HashSet como auxiliar)
    let mut vistos = HashSet::new();
    let sem_duplicatas: Vec<&str> = emails
        .iter()
        .copied()
        .filter(|email| vistos.insert(*email))
        .collect();

    println!("Sem duplicatas (ordem mantida): {:?}", sem_duplicatas);
    // ["ana@email.com", "bruno@email.com", "carla@email.com", "diana@email.com"]
}

5. HashSet com Tipos Customizados

use std::collections::HashSet;

#[derive(Debug, PartialEq, Eq, Hash)]
struct Ponto {
    x: i32,
    y: i32,
}

fn main() {
    let mut pontos: HashSet<Ponto> = HashSet::new();

    pontos.insert(Ponto { x: 0, y: 0 });
    pontos.insert(Ponto { x: 1, y: 2 });
    pontos.insert(Ponto { x: 0, y: 0 }); // duplicata, não será inserido

    println!("Total de pontos: {}", pontos.len()); // 2

    let busca = Ponto { x: 1, y: 2 };
    println!("Contém (1,2)? {}", pontos.contains(&busca)); // true

    // retain: manter apenas pontos com x positivo
    pontos.retain(|p| p.x > 0);
    println!("Após filtro: {:?}", pontos);
}

Características de Desempenho

OperaçãoComplexidade
insertO(1) amortizado
removeO(1) amortizado
containsO(1) amortizado
unionO(n + m)
intersectionO(min(n, m))
differenceO(n)
symmetric_differenceO(n + m)
is_subsetO(n)
is_disjointO(min(n, m))

Pior caso: O(n) para operações individuais — ocorre quando muitas chaves colidem no hash. Na prática, o hasher padrão (SipHash) distribui bem as chaves.

Memória: O HashSet aloca uma tabela hash, então usa mais memória que um Vec com os mesmos elementos. Use with_capacity() quando souber o tamanho aproximado.

HashSet vs BTreeSet

CritérioHashSetBTreeSet
Ordem de iteraçãoNão determinísticaCrescente
Requisito do tipoEq + HashOrd
Busca/Inserção/RemoçãoO(1) amortizadoO(log n)
Range queriesNãoSim
Operações de conjuntoSimSim
Uso de memóriaMaiorMenor

Veja Também