Vetor de zeros em Rust: não tão simples quanto parece
\n\nParece 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.
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\nPor que isso é mais complexo do que em Python?
\n\nEm 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".
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\nMétodo nº1: O clássico vec![0; size]
\n\nA forma mais óbvia e recomendada é usar a macro vec!:
let zeros: Vec<i32> = vec![0; 10];\nprintln!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n\nCurto, elegante, claro. Mas o que acontece nos bastidores? Vamos analisar passo a passo.
\n\nNos bastidores de vec![0; 10]
\n\nQuando você escreve vec![0; 10], o Rust faz o seguinte:
- \n
- Aloca memória no heap para 10 elementos do tipo
i32. Cadai32ocupa 4 bytes, totalizando 40 bytes. \n - Verifica se o tipo implementa a trait
Clone. Númerosi32a implementam, então o compilador pode copiar o valor0para cada elemento. \n - Inicia um loop de cópia dentro de
alloc::vec::from_elem. O compilador otimiza esse loop paramemset(se o tipo for escalar simples). \n - Retorna um
Veccom comprimento 10 e capacidade 10. \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\nMétodo nº2: vec![Default::default(); size]
\n\nSe o seu tipo não é um número, mas implementa a trait Default, você pode usar uma abordagem genérica:
#[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\nPara tipos primitivos, Default::default() retorna 0, 0.0, false ou '\\0'. Para structs personalizadas, retorna o que você definiu em #[derive(Default)].
E quanto ao desempenho?
\n\nVamos escrever um benchmark simples (em código real, use criterion):
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\nEm