Criação de vetor de zeros em Rust: a mágica por baixo dos panos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Vetor de zeros em Rust: não tão simples quanto parece

\n\n

Parece simples, não é? Criar um vetor preenchido com zeros. Em Python, faz-se com uma linha: [0] * n. Em JavaScript — Array(n).fill(0). Mas quando você migra para Rust, até mesmo uma tarefa tão trivial abre todo um mundo de programação de sistemas, gerenciamento de memória e abstrações de custo zero.

\n\n

A pergunta "Creating a vector of zeros for a specific size" no Stack Overflow acumulou milhares de visualizações. E não é por acaso: por trás de uma solicitação simples, esconde-se uma compreensão profunda de como o Rust lida com memória, inicialização e segurança. Hoje, vamos mergulhar nos bastidores do Rust e analisar todas as formas de criar um vetor de zeros e, principalmente, entender o que realmente acontece na memória RAM.

\n\n

Por que isso é mais complexo do que em Python?

\n\n

Em Python ou JavaScript, você simplesmente cria um array, e o interpretador faz todo o trabalho pesado para você. Rust, por outro lado, é uma linguagem de nível de sistema. Ela não permite deixar a memória não inicializada (a menos que você use unsafe). Cada byte deve ser explicitamente preenchido ou marcado como "comportamento indefinido".

\n\n

Imagine: você encomenda 100 caixas vazias. Em Python, elas simplesmente chegam vazias. Em Rust, o entregador pergunta: "O que colocar dentro de cada uma?" — e se você não disser, ele simplesmente não vem. Isso garante que você nunca leia acidentalmente "lixo" da memória deixado por um programa anterior.

\n\n

Método nº1: O clássico vec![0; size]

\n\n

A forma mais óbvia e recomendada é usar a macro vec!:

\n\n
let zeros: Vec<i32> = vec![0; 10];\nprintln!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
\n\n

Curto, elegante, claro. Mas o que acontece nos bastidores? Vamos analisar passo a passo.

\n\n

Nos bastidores de vec![0; 10]

\n\n

Quando você escreve vec![0; 10], o Rust faz o seguinte:

\n\n
    \n
  1. Aloca memória no heap para 10 elementos do tipo i32. Cada i32 ocupa 4 bytes, totalizando 40 bytes.
  2. \n
  3. Verifica se o tipo implementa a trait Clone. Números i32 a implementam, então o compilador pode copiar o valor 0 para cada elemento.
  4. \n
  5. Inicia um loop de cópia dentro de alloc::vec::from_elem. O compilador otimiza esse loop para memset (se o tipo for escalar simples).
  6. \n
  7. Retorna um Vec com comprimento 10 e capacidade 10.
  8. \n
\n\n

Ponto-chave: a memória não permanece não inicializada. O Rust garante que cada byte será preenchido com zeros antes que você tenha acesso ao vetor.

\n\n

Método nº2: vec![Default::default(); size]

\n\n

Se o seu tipo não é um número, mas implementa a trait Default, você pode usar uma abordagem genérica:

\n\n
#[derive(Default)]\nstruct Point {\n    x: f64,\n    y: f64,\n}\n\nlet points: Vec<Point> = vec![Default::default(); 5];\nprintln!("{:?}", points[0]); // Point { x: 0.0, y: 0.0 }
\n\n

Para tipos primitivos, Default::default() retorna 0, 0.0, false ou '\\0'. Para structs personalizadas, retorna o que você definiu em #[derive(Default)].

\n\n

E quanto ao desempenho?

\n\n

Vamos escrever um benchmark simples (em código real, use criterion):

\n\n
use std::time::Instant;\n\nfn main() {\n    let n = 1_000_000;\n\n    let start = Instant::now();\n    let v1 = vec![0u8; n];\n    let dur1 = start.elapsed();\n\n    let start = Instant::now();\n    let v2 = vec![0u64; n];\n    let dur2 = start.elapsed();\n\n    println!("u8:  {:?}", dur1);\n    println!("u64: {:?}", dur2);\n}
\n\n

Em

Blogs

Book Recommendations