Introdução
Uma coleção é uma estrutura de dados que armazena múltiplos valores na Heap, permitindo que a quantidade de itens mude durante a execução do programa.
Os principais tipos disponíveis em Rust são:
- Vec<T>: Array dinâmico;
- Stack (via Vec): Pilha LIFO;
- VecDeque<T>: Fila FIFO / Deque;
- HashMap<K,V>: Chave-valor (Hash);
- HashSet<T>: Conjunto (Hash);
- BinaryHeap<T>: Fila de prioridade;
- BTreeMap<K,V>: Chave-valor (ordenado);
- BTreeSet<T>: Conjunto (ordenado);
- LinkedList<T>: Lista encadeada.
Básico
Antes de começar, é importante definir alguns conceitos comuns que vão aparecer ao longo do texto:
- <T> (Type):
- Ao criar um vetor em Rust, podemos omitir o tipo quando o compilador consegue inferir pelo contexto, porém o recomendável é colocá-lo explicitamente.
- <K,V> (Key, Value):
- O conceito de chave-valor é bem simples: imagine que ao chegar no trabalho você encontra diversos armários e cada um deles possui uma chave única, o valor é o que você armazena dentro do armário. Em Rust, todas as chaves devem ser do mesmo tipo e todos os valores devem ser do mesmo tipo.
- O(1):
- O tempo é constante independente da quantidade de dados. Imagine o armário com etiquetas: para pegar algo, você vai direto na etiqueta certa, não importa se tem 10 ou 10 milhões de armários.
- O(n):
- O tempo é linear, cresce proporcionalmente à quantidade de dados. Sem etiquetas, você precisa abrir os armários um por um até encontrar o que quer. Se tiver 10, abre até 10. Se tiver 1000, abre até 1000.
- LIFO (Last in, first out):
- O último a entrar é o primeiro a sair. Imagine uma pilha de pratos: à medida que vão sujando e sendo colocados para lavar, o último prato a entrar será o primeiro a ser lavado.
- FIFO (First in, first out):
- O primeiro a entrar é o primeiro a sair. Pense em uma fila de supermercado: a primeira pessoa a chegar no caixa é a primeira a ser atendida.
Vec<T>
Pode aumentar ou diminuir de tamanho conforme a entrada e saída de dados. Os dados podem ser inseridos ou removidos de qualquer posição do vetor.
fn main () {
let mut vetor_str: Vec<&str> = vec!["a", "b", "c"];
println!("Vetor = {:#?}", vetor_str);
// inserindo dados
vetor_str.insert(1, "D");
println!("Vetor = {:#?}", vetor_str);
// removendo dados
vetor_str.remove(1);
println!("Vetor = {:#?}", vetor_str);
}
Stack
Não temos a opção "Stack (Pilha)" separada em Rust, pois o próprio Vec já possui esse comportamento. Vimos no exemplo anterior a adição de dados em qualquer posição do vetor, mas ele também pode se comportar como uma pilha.
fn main () {
let mut stack_str: Vec<&str> = vec!["a", "b", "c"];
println!("Stack = {:#?}", stack_str);
// push() - inserindo dados
stack_str.push("D");
println!("Stack = {:#?}", stack_str);
// pop() - removendo dados
stack_str.pop();
println!("Stack = {:#?}", stack_str);
// last() - último item
println!("Stack = {:#?}", stack_str.last());
}
VecDeque<T>
É um vetor excelente quando você precisa manipular elementos nos dois extremos da coleção com frequência. Funciona como uma fila dupla (double-ended queue), o que significa que você pode adicionar e remover elementos tanto no início quanto no final de forma eficiente.
use std::collections::VecDeque;
fn main() {
let mut vecdeque: VecDeque<i32> = VecDeque::new();
// push_back() - inserindo dados no final
vecdeque.push_back(10);
vecdeque.push_back(20);
// push_front() - inserindo dados no início
vecdeque.push_front(5);
// [5, 10, 20]
println!("VecDeque: {:?}", vecdeque);
// pop_front() - removendo do início
let primeiro = vecdeque.pop_front();
println!("Removido do início: {:?}", primeiro); // Some(5)
// pop_back() - removendo do fim
let ultimo = vecdeque.pop_back();
println!("Removido do fim: {:?}", ultimo); // Some(20)
println!("Restou apenas: {:?}", vecdeque); // [10]
}
HashMap<K,V>
O HashMap é a forma mais rápida de acessar um valor a partir de uma chave. Lembra do exemplo do armário? A partir do momento que você guarda algo e atribui a esse item uma chave, você pode acessar o dado rapidamente, independente do tamanho do mapa.
use std::collections::HashMap;
fn main() {
let mut config: HashMap<&str, &str> = HashMap::new();
// insert - inserindo dados
config.insert("tema", "escuro");
println!("Tema: {:?}", config.get("tema"));
// entry - só serão inseridos dados se a chave estiver vazia
config.entry("idioma").or_insert("português");
println!("Idioma: {:?}", config.get("idioma"));
// "tema" já é "escuro", ele vai ignorar o "claro"
config.entry("tema").or_insert("claro");
println!("Tema: {:?}", config.get("tema"));
println!("Configurações atuais:");
println!("Tema: {:?}", config.get("tema"));
println!("Idioma: {:?}", config.get("idioma"));
}
HashSet<T>
Quando você entende bem o conceito de chave e valor, fica fácil entender o HashSet, aqui só existe o valor e ele não pode se repetir. Imagine um sistema com uma lista de CPFs de diversos usuários o CPF de 2 usuários não pode ser igual.
use std::collections::HashSet;
fn main() {
// criando um HashSet de Strings
let mut nomes: HashSet<&str> = HashSet::new();
// adicionando nomes
nomes.insert("Alice");
nomes.insert("Bruno");
nomes.insert("Carla");
println!("{:#?}", nomes);
let adicionando_novamente = nomes.insert("Alice");
println!("Conseguiu adicionar 'Alice'? {}", adicionando_novamente);
// removendo alguém
nomes.remove("Bruno");
// verificando se contém na lista
println!("Existe 'Carla' na lista? {}", nomes.contains("Carla"));
}
BinaryHeap<T>
Se você está criando uma lista em que a prioridade é algo importante, o BinaryHeap acaba sendo a
melhor opção. Ele é extremamente eficiente quando precisamos acessar o maior ou o menor valor.
use std::collections::BinaryHeap;
fn main() {
let mut fila_hospital: BinaryHeap<u8> = BinaryHeap::new();
// entrada aleatória por idade
fila_hospital.push(25); // jovem
fila_hospital.push(80); // idoso (vai para o topo)
fila_hospital.push(40); // meia-idade
fila_hospital.push(95); // muito idoso (vai para o topo agora!)
println!("Estado interno: {:?}", fila_hospital);
// o próximo a entrar
// mesmo que o 95 tenha sido o ÚLTIMO a chegar, ele será o PRIMEIRO a sair
while let Some(paciente) = fila_hospital.pop() {
println!("Atendendo paciente de {} anos.", paciente);
}
}
BTreeMap<K,V>
O BTreeMap é como juntar o "HashMap" com o "BinaryHeap", com uma pequena diferença: os valores ao entrar se organizam completamente, enquanto no "BinaryHeap" eles apenas se reorganizam para o maior ou menor ficar facilmente acessível.
use std::collections::BTreeMap;
fn main() {
// criando mapa
let mut chamados: BTreeMap<u8, &str> = BTreeMap::new();
// inserindo dados de forma "desorganizada"
chamados.insert(3, "Bug crítico no login");
chamados.insert(1, "Trocar cor do botão");
chamados.insert(5, "Explosão no servidor");
chamados.insert(2, "Ajustar rodapé");
// as chaves se organizam automaticamente: 1, 2, 3, 5
println!("Fila de chamados (ordenada por ID):");
for (id, tarefa) in &chamados {
println!("ID {}: {}", id, tarefa);
}
}
BTreeSet<T>
O BTreeSet funciona seguindo a mesma lógica do "BTreeMap", porém sem valores associados, apenas chaves. Pense em uma lista de convidados: por mais que tente adicionar o mesmo nome várias vezes, ele guarda apenas um. Não importa a ordem em que foi adicionada, a lista se organiza sozinha. A busca por um nome ou grupo de nomes também é muito eficiente, pois os dados ficam organizados em ordem na árvore, evitando percorrer a lista inteira.
use std::collections::BTreeSet;
fn main() {
// lista de convidados
let mut convidados: BTreeSet<&str> = BTreeSet::new();
// adicionamos pessoas em ordem aleatória
convidados.insert("Tiago");
convidados.insert("Ana");
convidados.insert("Bruno");
// tentamos adicionar o mesmo nome várias vezes
convidados.insert("Ana");
convidados.insert("Tiago");
// lista se organiza sozinha
// duplicatas são ignoradas automaticamente
println!("Lista oficial de convidados: {:?}", convidados);
}
LinkedList<T>
Quando você tem uma coleção em que precisa inserir ou remover elementos no meio com frequência, a "LinkedList" pode ser uma boa opção. Você cria um link entre os elementos, que pode ser simplesmente encadeada (cada elemento conhece apenas o próximo) ou duplamente encadeada (cada elemento conhece o anterior e o próximo).
use std::collections::LinkedList;
fn main() {
// lista de pessoas
let mut lista: LinkedList<&str> = LinkedList::new();
// push_back adiciona no final
lista.push_back("Carlos");
lista.push_back("João");
lista.push_back("Pedro");
// push_front adiciona no início
lista.push_front("Ana");
// Ana → Carlos → João → Pedro
println!("{:?}", lista);
}
Tabela Comparativa
| Estrutura | Inserção/Remoção | Acesso | Observação |
|---|---|---|---|
| Vec<T> | O(1) no final | O(1) por índice | O(n) ao redimensionar |
| Vec<T> como Stack | O(1) no final | O(1) no topo | O(n) ao redimensionar |
| VecDeque<T> | O(1) nos dois extremos | O(1) por índice | O(n) ao redimensionar |
| HashMap<K, V> | O(1) médio | O(1) médio por chave | Sem ordem garantida |
| HashSet<T> | O(1) médio | O(1) médio | Sem ordem, sem duplicatas |
| BinaryHeap<T> | O(log n) | O(1) no máximo | Fila de prioridade |
| BTreeMap<K, V> | O(log n) | O(log n) por chave | Ordenado por chave |
| BTreeSet<T> | O(log n) | O(log n) | Ordenado, sem duplicatas |
| LinkedList<T> | O(1) nos extremos | O(n) por posição | Pouco usada em Rust |
Este material foi escrito para dar um panorama geral de como funcionam as coleções em Rust. Ainda será publicado um conteúdo explicando cada item detalhadamente. No livro você vai encontrar diversos exemplos e formas de uso para entender como aplicar isso no seu programa em desenvolvimento.
Caso queira continuar estudando e aprender mais sobre outros conteúdos, segue o link. Bons estudos!
"A beleza que vive no ato de compartilhar algo com o outro." — Monja Coen