Vetores (Vec<T>) são uma das estruturas de dados mais utilizadas em Rust. Eles representam uma lista dinâmica, mutável e alocada no heap, capaz de armazenar elementos de um mesmo tipo. Diferente de arrays ([T; N]), o tamanho de um vetor pode crescer ou diminuir em tempo de execução. Nesta aula, exploraremos a criação, manipulação, acesso seguro, iteração e aspectos internos de capacidade e realocação.

Entender o funcionamento de Vec é essencial para escrever código Rust idiomático e eficiente. Veremos desde operações básicas até detalhes de implementação que afetam performance.

Criação e push

Para criar um vetor, podemos usar Vec::new() ou a macro vec!. A função 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);
println!("{:?}", v); // [1, 2, 3]

Também é comum inicializar com valores usando vec!:

let v = vec![1, 2, 3];
// v é imutável, mas ainda podemos ler

Note que vec! é uma macro que gera um Vec com os elementos fornecidos. O tipo é inferido automaticamente.

Indexação vs get

Podemos acessar elementos de um vetor por índice usando colchetes (v[i]) ou o método get(i). A diferença fundamental é que a indexação direta causa pânico (panic) se o índice estiver fora dos limites, enquanto get retorna Option<&T>.

let v = vec![10, 20, 30];
// Indexação direta (pode panicar)
let first = &v[0]; // ok
// let out = &v[5]; // panic!

// Usando get (seguro)
match v.get(2) {
    Some(val) => println!("Valor: {}", val),
    None => println!("Índice inválido"),
}

Recomenda-se usar get quando não houver certeza sobre a validade do índice, especialmente em código que lida com entrada do usuário ou índices calculados.

Iteração

Iterar sobre um vetor pode ser feito de várias formas. A mais comum é usar um loop for sobre uma referência ao vetor. Exemplo:

let v = vec![1, 2, 3];

// Iteração por referência (imutável)
for elem in &v {
    println!("{}", elem);
}

// Iteração mutável
let mut v = vec![1, 2, 3];
for elem in &mut v {
    *elem *= 2;
}
println!("{:?}", v); // [2, 4, 6]

// Consumindo o vetor (move)
for elem in v {
    println!("{}", elem);
}
// v não pode mais ser usado

Também podemos usar métodos como iter(), iter_mut(), into_iter() e adaptadores como map, filter, etc.

Capacidade e realocação

Todo vetor possui uma capacidade (capacity) que é o número de elementos que pode armazenar sem alocar mais memória. Quando a capacidade é excedida, o vetor realoca uma nova região de memória (geralmente dobrando a capacidade) e copia os elementos. Isso pode ser custoso.

let mut v: Vec<i32> = Vec::with_capacity(2);
println!("Capacidade inicial: {}", v.capacity()); // 2
v.push(1);
v.push(2);
println!("Capacidade após 2 pushes: {}", v.capacity()); // 2
v.push(3); // realoca
println!("Capacidade após 3 pushes: {}", v.capacity()); // 4 (dobrou)

Para evitar realocações frequentes, podemos usar Vec::with_capacity ou reserve para alocar espaço de antemão. A capacidade nunca é menor que o comprimento (len).

Boa prática: se você sabe o número aproximado de elementos, use with_capacity para melhorar performance.

Boas práticas e observações finais

Sempre prefira métodos seguros como get quando houver dúvida sobre índices. Use iteração por referência para evitar movimentação desnecessária. Lembre-se de que Vec é uma estrutura ownership: quando um vetor é movido, seus dados também são movidos. Para compartilhamento, use referências.

Vetores são a base para muitas outras estruturas de dados em Rust. Dominar seu uso é fundamental.

Referências

Exercícios

  1. Crie um vetor vazio do tipo String e adicione três strings: "Rust", "é", "legal". Imprima o vetor.

    ✓ Resposta:
    let mut v: Vec<String> = Vec::new();
    v.push("Rust".to_string());
    v.push("é".to_string());
    v.push("legal".to_string());
    println!("{:?}", v);
  2. Dado o vetor let v = vec![5, 10, 15, 20];, acesse o elemento no índice 2 usando indexação direta e usando get. O que acontece se você tentar acessar o índice 10?

    ✓ Resposta:
    let v = vec![5, 10, 15, 20];
    // Indexação direta
    println!("{}", v[2]); // 15
    // get
    match v.get(2) {
        Some(val) => println!("{}", val),
        None => println!("None"),
    }
    // Tentativa com índice 10:
    // v[10] -> panic!
    // v.get(10) -> retorna None
  3. Escreva um loop que itere sobre um vetor de números e imprima o dobro de cada um. Use iteração mutável para modificar o vetor original.

    ✓ Resposta:
    let mut v = vec![1, 2, 3, 4];
    for elem in &mut v {
        *elem *= 2;
    }
    println!("{:?}", v); // [2, 4, 6, 8]
  4. Crie um vetor com capacidade inicial 10. Adicione 5 elementos e verifique a capacidade. Explique por que a capacidade não mudou.

    ✓ Resposta:
    let mut v: Vec<i32> = Vec::with_capacity(10);
    println!("Capacidade inicial: {}", v.capacity()); // 10
    v.push(1); v.push(2); v.push(3); v.push(4); v.push(5);
    println!("Capacidade após 5 pushes: {}", v.capacity()); // 10
    // A capacidade não mudou porque o número de elementos (5) é menor que a capacidade (10).
    // Realocação só ocorre quando len == capacity e um novo push é feito.
  5. Escreva uma função que recebe um vetor de inteiros e retorna a soma de todos os elementos. Use iteração por referência.

    ✓ Resposta:
    fn soma(v: &Vec<i32>) -> i32 {
        let mut total = 0;
        for elem in v {
            total += elem;
        }
        total
    }
    
    fn main() {
        let v = vec![1, 2, 3, 4, 5];
        println!("{}", soma(&v)); // 15
    }