Vectors: coleções dinâmicas
1. Introdução aos Vectors
Em Rust, Vec<T> é a principal estrutura de dados para coleções dinâmicas alocadas no heap. Diferente dos arrays [T; N], que têm tamanho fixo conhecido em tempo de compilação, os vectors podem crescer ou encolher dinamicamente durante a execução do programa.
A principal diferença entre vectors e slices &[T] é que vectors possuem ownership dos dados, enquanto slices são apenas visualizações (views) de uma sequência de elementos. Slices são imutáveis por padrão, a menos que sejam declarados como &mut [T].
Quando usar Vectors:
- Quando o número de elementos é desconhecido em tempo de compilação
- Quando você precisa adicionar ou remover elementos frequentemente
- Quando trabalha com dados que variam de tamanho em tempo de execução
2. Criando e Inicializando Vectors
Rust oferece várias formas de criar vectors:
// Vector vazio
let mut vazio: Vec<i32> = Vec::new();
// Usando a macro vec!
let numeros = vec![1, 2, 3, 4, 5];
// Inicialização com valores padrão
let zeros = vec![0; 5]; // vec![0, 0, 0, 0, 0]
// Coletando de um iterador
let quadrados: Vec<i32> = (1..=5).map(|x| x * x).collect();
println!("{:?}", quadrados); // [1, 4, 9, 16, 25]
// Especificando o tipo explicitamente
let coletado: Vec<_> = (0..10).filter(|x| x % 2 == 0).collect();
3. Adicionando e Removendo Elementos
Operações básicas de manipulação:
let mut frutas = Vec::new();
// Adicionando no final
frutas.push("maçã");
frutas.push("banana");
frutas.push("laranja");
println!("{:?}", frutas); // ["maçã", "banana", "laranja"]
// Removendo do final
let ultima = frutas.pop();
println!("Removido: {:?}", ultima); // Some("laranja")
// Inserindo em posição específica
frutas.insert(1, "uva");
println!("{:?}", frutas); // ["maçã", "uva", "banana"]
// Removendo de posição específica
let removida = frutas.remove(0);
println!("Removido: {:?}", removida); // "maçã"
Considerações de desempenho: .push() e .pop() são operações O(1) amortizado. Já .insert() e .remove() são O(n) pois exigem deslocamento de elementos.
4. Acessando Elementos com Segurança
Rust prioriza segurança de memória, oferecendo duas formas de acesso:
let dados = vec![10, 20, 30, 40, 50];
// Indexação direta (pode causar panic!)
let primeiro = dados[0]; // 10
// Acesso seguro com .get() (retorna Option)
match dados.get(10) {
Some(valor) => println!("Valor: {}", valor),
None => println!("Índice fora dos limites"),
}
// Slice patterns
let slice = &dados[1..4]; // [20, 30, 40]
println!("Slice: {:?}", slice);
// Iteração segura
for (indice, valor) in dados.iter().enumerate() {
println!("dados[{}] = {}", indice, valor);
}
// Iteração mutável
let mut mutavel = vec![1, 2, 3];
for valor in mutavel.iter_mut() {
*valor *= 2;
}
println!("{:?}", mutavel); // [2, 4, 6]
5. Capacidade e Gerenciamento de Memória
Entender a diferença entre capacidade e tamanho é crucial para performance:
let mut vec = Vec::with_capacity(10);
println!("Capacidade: {}, Tamanho: {}", vec.capacity(), vec.len());
for i in 0..15 {
vec.push(i);
}
println!("Após 15 pushes - Capacidade: {}, Tamanho: {}",
vec.capacity(), vec.len());
// Reservando mais espaço
vec.reserve(20);
println!("Após reserve(20) - Capacidade: {}", vec.capacity());
// Reduzindo capacidade ao mínimo necessário
vec.shrink_to_fit();
println!("Após shrink_to_fit - Capacidade: {}", vec.capacity());
Estratégia de crescimento: Quando a capacidade é excedida, Rust dobra a capacidade (ou aloca um novo buffer com tamanho max(2*old_cap, new_len)).
6. Ordenação e Transformações
let mut numeros = vec![5, 2, 8, 1, 9, 3];
// Ordenação crescente
numeros.sort();
println!("Ordenado: {:?}", numeros); // [1, 2, 3, 5, 8, 9]
// Ordenação decrescente com sort_by
numeros.sort_by(|a, b| b.cmp(a));
println!("Decrescente: {:?}", numeros); // [9, 8, 5, 3, 2, 1]
// Inversão
numeros.reverse();
println!("Invertido: {:?}", numeros); // [1, 2, 3, 5, 8, 9]
// Remoção de duplicatas (requer ordenação)
let mut duplicatas = vec![1, 2, 2, 3, 3, 3, 4];
duplicatas.dedup();
println!("Sem duplicatas: {:?}", duplicatas); // [1, 2, 3, 4]
// Transformações com iteradores
let pares: Vec<i32> = numeros.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 10)
.collect();
println!("Pares transformados: {:?}", pares); // [20, 80]
// Fold (reduce)
let soma: i32 = numeros.iter().fold(0, |acc, &x| acc + x);
println!("Soma: {}", soma); // 28
7. Vectors e Ownership
O sistema de ownership do Rust se aplica claramente aos vectors:
fn processar_vec(v: Vec<i32>) {
println!("Processando: {:?}", v);
// v é dropado aqui
}
fn main() {
let original = vec![1, 2, 3];
// Movendo ownership (consumindo o vector)
processar_vec(original);
// println!("{:?}", original); // Erro! original foi movido
// Clonagem explícita
let clonado = vec![4, 5, 6].clone();
processar_vec(clonado.clone());
println!("Original ainda disponível: {:?}", clonado);
// Empréstimo (borrowing)
let emprestado = vec![7, 8, 9];
let tamanho = emprestado.len(); // &self
println!("Tamanho: {}", tamanho);
println!("{:?}", emprestado); // Ainda podemos usar
// Iteração consumindo o vector
let consumir = vec![10, 20, 30];
for valor in consumir.into_iter() {
println!("{}", valor);
}
// consumir não está mais disponível
}
8. Boas Práticas e Padrões Comuns
Evitando realocações frequentes
// Ruim: pode causar múltiplas realocações
let mut ruim = Vec::new();
for i in 0..1000 {
ruim.push(i);
}
// Bom: pré-aloca capacidade necessária
let mut bom = Vec::with_capacity(1000);
for i in 0..1000 {
bom.push(i);
}
Combinando com HashMap e Result
use std::collections::HashMap;
// Sistema de notas de alunos
struct Aluno {
nome: String,
notas: Vec<f64>,
}
fn calcular_media(notas: &[f64]) -> Option<f64> {
if notas.is_empty() {
return None;
}
let soma: f64 = notas.iter().sum();
Some(soma / notas.len() as f64)
}
fn main() {
let mut alunos: HashMap<String, Aluno> = HashMap::new();
alunos.insert(
"João".to_string(),
Aluno {
nome: "João".to_string(),
notas: vec![8.5, 7.0, 9.2],
},
);
alunos.insert(
"Maria".to_string(),
Aluno {
nome: "Maria".to_string(),
notas: vec![9.0, 8.5, 9.8],
},
);
// Operações CRUD
for (_, aluno) in &alunos {
match calcular_media(&aluno.notas) {
Some(media) => println!("{}: média {:.2}", aluno.nome, media),
None => println!("{}: sem notas", aluno.nome),
}
}
// Adicionando nota
if let Some(aluno) = alunos.get_mut("João") {
aluno.notas.push(10.0);
println!("Nota adicionada para João");
}
// Removendo última nota
if let Some(aluno) = alunos.get_mut("Maria") {
if let Some(ultima) = aluno.notas.pop() {
println!("Última nota de Maria removida: {}", ultima);
}
}
}
Referências
- Documentação oficial de Vec — Documentação completa da struct Vec com todos os métodos disponíveis
- Rust by Example: Vectors — Tutoriais práticos e exemplos interativos de uso de vectors
- The Rust Programming Language - Capítulo 8.1 — Seção oficial do livro sobre vectors com explicações detalhadas
- Rust Vec cheatsheet — Referência rápida com exemplos de todos os métodos principais de Vec
- Rust Collections Performance — Guia de performance para escolher entre diferentes coleções em Rust
- Effective Rust: Vec — Dicas avançadas e padrões de uso eficiente de vectors em Rust