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