Vetores (Vec)
Nesta aula, você aprenderá a trabalhar com vetores (Vec) em Rust, incluindo criação, adição de elementos, acesso seguro por índice, iteração, e gerenciamento de capacidade e realocação.
Vetores (Vec) são uma das estruturas de dados mais fundamentais e versáteis em Rust. Eles representam uma coleção contígua de elementos do mesmo tipo, armazenada na heap, que pode crescer ou encolher dinamicamente. Diferente de arrays ([T; N]), cujo tamanho é fixo em tempo de compilação, vetores são alocados dinamicamente e podem ter seu tamanho alterado em tempo de execução. Nesta aula, exploraremos desde a criação básica até tópicos avançados como capacidade e realocação.
Entender o funcionamento interno de Vec é crucial para escrever código eficiente e seguro. Rust fornece garantias de segurança de memória sem sacrificar performance, e o uso correto de vetores é um exemplo clássico disso. Vamos mergulhar nos detalhes.
Criação e push
Para criar um vetor vazio, usamos Vec::new() ou a macro vec![]. A macro vec! também permite inicializar o vetor com elementos. O método push adiciona um elemento ao final do vetor. Exemplo:
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);
let v2 = vec![4, 5, 6]; // usando a macro
println!("{:?}", v); // [1, 2, 3]
println!("{:?}", v2); // [4, 5, 6]
Note que declaramos mut para poder modificar o vetor. push pode causar realocação se a capacidade for insuficiente. O tipo do vetor é inferido a partir dos elementos ou explicitamente anotado.
Indexação vs get
O acesso a elementos de um vetor pode ser feito de duas formas principais: indexação direta com v[index] e o método get(index). A indexação direta causa um panic se o índice estiver fora dos limites, enquanto get retorna Option<&T>, permitindo tratamento seguro.
let v = vec![10, 20, 30];
// Indexação direta (pode panic!)
let first = &v[0];
println!("Primeiro: {}", first);
// get retorna Option
match v.get(1) {
Some(val) => println!("Segundo: {}", val),
None => println!("Índice inválido"),
}
// Fora dos limites
// let out = &v[5]; // panic!
let safe = v.get(5);
match safe {
Some(val) => println!("Valor: {}", val),
None => println!("Índice 5 não existe"),
}
Recomenda-se usar get quando o índice pode ser inválido, e indexação direta apenas quando você tem certeza de que o índice é válido (por exemplo, após verificar o tamanho).
Iteração
Iterar sobre um vetor é simples usando loops for. Podemos iterar por referência imutável, mutável ou consumindo o vetor (tomando posse).
let v = vec![1, 2, 3];
// Iteração imutável (empresta &T)
for x in &v {
println!("{}", x);
}
// Iteração mutável (empresta &mut T)
let mut v = vec![1, 2, 3];
for x in &mut v {
*x += 10;
}
println!("{:?}", v); // [11, 12, 13]
// Consumindo o vetor (move para dentro do loop)
let v = vec![1, 2, 3];
for x in v {
println!("{}", x);
}
// v não pode mais ser usado aqui
Além disso, podemos usar métodos de iteração como iter(), iter_mut(), into_iter(), e adaptadores como map, filter, etc.
Capacidade e realocação
Um vetor possui dois atributos importantes: len() (número de elementos) e capacity() (espaço alocado na heap). Quando push é chamado e a capacidade é insuficiente, o vetor realoca um novo buffer maior (geralmente dobrando a capacidade) e copia os elementos para lá. Isso pode ser custoso, então é útil pré-alocar capacidade com Vec::with_capacity.
let mut v = Vec::with_capacity(10);
println!("Capacidade inicial: {}", v.capacity()); // 10
for i in 0..10 {
v.push(i);
}
println!("Capacidade após 10 pushes: {}", v.capacity()); // 10 (ainda)
v.push(11);
println!("Capacidade após 11º push: {}", v.capacity()); // 20 (dobrou)
// shrink_to_fit reduz a capacidade para o tamanho atual
v.shrink_to_fit();
println!("Capacidade após shrink: {}", v.capacity()); // 11
Entender a capacidade ajuda a otimizar o uso de memória e evitar realocações desnecessárias. Use with_capacity quando souber quantos elementos serão inseridos.
Boas práticas
- Prefira
vec![val; n]para inicializar com valores padrão. - Use
Vec::with_capacitypara evitar múltiplas realocações. - Sempre prefira
getsobre indexação direta quando o índice for variável. - Evite manter referências ao vetor enquanto o modifica (regras de borrowing).
- Considere usar slices (
&[T]) para funções que só precisam ler os dados.
Referências
- Documentação oficial do Vec
- The Rust Programming Language - Capítulo 8: Vetores
- Rust by Example - Vectors
- The Rustonomicon - Vec implementação
- Rust 101 - Vetores e slices
Exercícios
-
Crie um vetor vazio de inteiros e adicione os números 1, 2 e 3 usando push. Depois imprima o vetor.
✓ Resposta:let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); println!("{:?}", v); -
Dado o vetor
let v = vec![10, 20, 30, 40, 50];, acesse o elemento no índice 2 usando indexação direta e o elemento no índice 10 usandoget. Imprima ambos de forma segura.✓ Resposta:let v = vec![10, 20, 30, 40, 50]; let idx2 = &v[2]; println!("Índice 2: {}", idx2); match v.get(10) { Some(val) => println!("Índice 10: {}", val), None => println!("Índice 10 não existe"), } -
Itere sobre um vetor
let mut v = vec![1, 2, 3];e multiplique cada elemento por 2 (modificando o vetor). Depois imprima o vetor modificado.✓ Resposta:let mut v = vec![1, 2, 3]; for x in &mut v { *x *= 2; } println!("{:?}", v); // [2, 4, 6] -
Crie um vetor com capacidade para 5 elementos usando
with_capacity. Adicione 5 elementos e verifique a capacidade. Em seguida, adicione um sexto elemento e veja como a capacidade muda.✓ Resposta:let mut v = Vec::with_capacity(5); println!("Capacidade inicial: {}", v.capacity()); for i in 0..5 { v.push(i); } println!("Capacidade após 5 pushes: {}", v.capacity()); v.push(5); println!("Capacidade após 6º push: {}", v.capacity()); // geralmente dobra para 10 -
Escreva uma função que recebe um vetor de inteiros e retorna a soma de todos os elementos. Use iteração por referência. Teste com
vec![1, 2, 3].✓ Resposta:fn soma(v: &Vec<i32>) -> i32 { let mut total = 0; for x in v { total += x; } total } fn main() { let v = vec![1, 2, 3]; println!("Soma: {}", soma(&v)); }