Rust Admin

Guia Completo de Vec<T> em Rust: O Array Dinâmico da Biblioteca Padrão Já leu

Vec : Entendendo o Array Dinâmico Fundamental Vec é a estrutura de dados mais versátil da Rust: um array alocado na heap que cresce e encolhe dinamicamente conforme necessário. Diferentemente de arrays primitivos como , que têm tamanho fixo e conhecido em tempo de compilação, Vec permite que você trabalhe com coleções cujo tamanho é desconhecido ou muda durante a execução. Internamente, Vec mantém três informações críticas: um ponteiro para os dados na heap, a capacidade (quantos elementos cabem sem realocação) e o comprimento (quantos elementos estão realmente armazenados). Essa distinção entre capacidade e comprimento é essencial para entender a performance e o comportamento de Vec. Criação, Inicialização e Operações Básicas Existem várias formas ergonômicas de criar um Vec em Rust. A macro é a mais prática para inicializar com valores conhecidos, enquanto cria um vetor vazio. O método é crucial quando você sabe antecipadamente quantos elementos será necessário armazenar, evitando realocações desnecessárias. As operações de indexação usam , mas

Vec: Entendendo o Array Dinâmico Fundamental

Vec é a estrutura de dados mais versátil da Rust: um array alocado na heap que cresce e encolhe dinamicamente conforme necessário. Diferentemente de arrays primitivos como [T; n], que têm tamanho fixo e conhecido em tempo de compilação, Vec permite que você trabalhe com coleções cujo tamanho é desconhecido ou muda durante a execução.

Internamente, Vec mantém três informações críticas: um ponteiro para os dados na heap, a capacidade (quantos elementos cabem sem realocação) e o comprimento (quantos elementos estão realmente armazenados). Essa distinção entre capacidade e comprimento é essencial para entender a performance e o comportamento de Vec.

fn main() {
    // Criando um Vec vazio
    let mut numeros: Vec<i32> = Vec::new();

    // Adicionando elementos
    numeros.push(10);
    numeros.push(20);
    numeros.push(30);

    println!("Vec: {:?}", numeros);
    println!("Comprimento: {}", numeros.len());
    println!("Capacidade: {}", numeros.capacity());
}

Criação, Inicialização e Operações Básicas

Existem várias formas ergonômicas de criar um Vec em Rust. A macro vec! é a mais prática para inicializar com valores conhecidos, enquanto Vec::new() cria um vetor vazio. O método with_capacity() é crucial quando você sabe antecipadamente quantos elementos será necessário armazenar, evitando realocações desnecessárias.

fn main() {
    // Usando macro vec!
    let mut cores = vec!["vermelho", "azul", "verde"];

    // Pré-alocando capacidade
    let mut dados: Vec<String> = Vec::with_capacity(100);

    // Acessando elementos
    println!("Primeira cor: {}", cores[0]);

    // Iterando
    for cor in &cores {
        println!("{}", cor);
    }

    // Modificando
    cores.push("amarelo");
    cores[0] = "laranja";

    // Removendo
    let removido = cores.pop(); // Remove o último
    println!("Removido: {:?}", removido);
}

As operações de indexação usam [], mas falham em runtime se o índice estiver fora dos limites. Para segurança, use get(), que retorna Option<&T>. O método pop() remove e retorna o último elemento, enquanto insert() e remove() trabalham com índices específicos — porém são custosos para vetores grandes porque requerem realocação de memória.

Propriedade, Empréstimo e Iteração Eficiente

A relação entre Vec e o sistema de propriedade (ownership) de Rust é onde muitos iniciantes enfrentam dificuldades. Quando você passa um Vec para uma função sem usar referência, a propriedade é transferida e a variável original fica inacessível. Use &vec para empréstimos imutáveis e &mut vec para mutáveis.

fn processar_vetor(vec: &[i32]) -> i32 {
    vec.iter().sum()
}

fn duplicar_elementos(vec: &mut Vec<i32>) {
    for elemento in vec.iter_mut() {
        *elemento *= 2;
    }
}

fn main() {
    let mut numeros = vec![1, 2, 3, 4, 5];

    let soma = processar_vetor(&numeros);
    println!("Soma: {}", soma);

    duplicar_elementos(&mut numeros);
    println!("Duplicados: {:?}", numeros);

    // Iteração eficiente com into_iter (consome o vetor)
    let palavras = vec!["hello", "world"];
    for palavra in palavras {
        println!("{}", palavra);
    }
    // 'palavras' não está mais acessível aqui
}

A escolha entre iter(), iter_mut() e into_iter() é fundamental. Use iter() para não modificar e manter a propriedade; iter_mut() para modificar in-place; e into_iter() quando quiser consumir o vetor e obter a propriedade dos elementos. Operações que consomem o vetor como into_iter() são geralmente mais eficientes porque não precisam gerenciar referências.

Performance, Alocação de Memória e Boas Práticas

Vec crescer dinamicamente tem um custo: quando a capacidade é excedida, ele realoca a memória — copiando todos os elementos para um novo bloco maior na heap. Essa é uma operação O(n) que deve ser evitada ao máximo. Se você sabe que precisará armazenar 10.000 elementos, aloque essa capacidade desde o início com Vec::with_capacity(10_000).

fn main() {
    // Ineficiente: múltiplas realocações
    let mut lento = Vec::new();
    for i in 0..100_000 {
        lento.push(i);
    }

    // Eficiente: uma alocação única
    let mut rapido = Vec::with_capacity(100_000);
    for i in 0..100_000 {
        rapido.push(i);
    }

    // Reserve espaço adicional se necessário
    let mut reservado = vec![1, 2, 3];
    reservado.reserve(1000);

    // Métodos úteis
    println!("Está vazio? {}", reservado.is_empty());
    reservado.clear(); // Remove todos sem deallocar
    println!("Após clear: len={}, capacity={}", 
             reservado.len(), 
             reservado.capacity());
}

Métodos como reserve() pré-alocam espaço sem adicionar elementos; clear() remove todos os itens mas mantém a capacidade; e shrink_to_fit() reduce a capacidade ao comprimento atual, útil quando você sabe que não adicionará mais elementos. Use slices (&[T]) em assinaturas de funções em vez de &Vec<T> — slices são mais flexíveis e aceitam tanto referências para vetores quanto para arrays primitivos.

Conclusão

Vec é a espinha dorsal de estruturas de dados complexas em Rust. Os três conceitos essenciais que você dominou aqui são: (1) a distinção entre capacidade e comprimento, que determina a eficiência de suas operações; (2) o sistema de propriedade e empréstimo, que garante segurança de memória sem garbage collector; (3) a importância de pré-alocar capacidade quando o tamanho final é conhecido. Domine essas lições e você terá ferramentas para escrever código Rust rápido e seguro.

Referências


Artigos relacionados