Introdução
Arrays e vetores são as estruturas de dados mais fundamentais em qualquer linguagem de programação.
Em Rust, temos duas formas principais de armazenar sequências contíguas de elementos: os arrays
de tamanho fixo [T; N] e os vetores dinâmicos Vec<T>. Compreender profundamente essas
estruturas é essencial, pois elas formam a base sobre a qual muitas outras estruturas são
construídas — pilhas, filas, heaps e até tabelas hash utilizam vetores internamente.
A grande vantagem dessas estruturas é a localidade de memória: como os elementos ficam lado a lado na memória, o acesso sequencial é extremamente eficiente, aproveitando ao máximo as linhas de cache do processador. Essa propriedade torna arrays e vetores a escolha padrão para a maioria dos casos de uso em programação de sistemas.
Neste artigo, exploraremos desde os conceitos teóricos até implementações práticas, incluindo um pipeline completo de processamento de dados CSV.
Conceito e Teoria
Array [T; N] — Tamanho Fixo
Um array em Rust é uma coleção de tamanho fixo, determinado em tempo de compilação. Todos os
elementos possuem o mesmo tipo T e o tamanho N faz parte do tipo.
Array [i32; 5] na Stack
┌─────────────────────────────────────────┐
│ Endereço base: 0x7FFE_1000 │
├────────┬────────┬────────┬────────┬────────┤
│ [0] │ [1] │ [2] │ [3] │ [4] │
│ 10 │ 20 │ 30 │ 40 │ 50 │
│ 4bytes │ 4bytes │ 4bytes │ 4bytes │ 4bytes │
├────────┴────────┴────────┴────────┴────────┤
│ Total: 5 × 4 = 20 bytes na stack │
└─────────────────────────────────────────┘
Acesso a [i]: endereço_base + i × tamanho_elemento
Exemplo [3]: 0x7FFE_1000 + 3 × 4 = 0x7FFE_100C
Vec — Vetor Dinâmico
O Vec<T> é composto por três campos armazenados na stack, enquanto os dados residem na heap:
Stack Heap
┌──────────────────┐ ┌────────────────────────────────────────┐
│ Vec<i32> │ │ Endereço: 0x5596_A000 │
│ ┌──────────────┐ │ ├────────┬────────┬────────┬────────┬───┤
│ │ ptr ─────────┼─┼────────► │ 10 │ 20 │ 30 │ ???? │ │
│ ├──────────────┤ │ │ usado │ usado │ usado │ livre │ │
│ │ len: 3 │ │ ├────────┴────────┴────────┴────────┴───┤
│ ├──────────────┤ │ │ Capacidade alocada: 4 elementos │
│ │ capacity: 4 │ │ └────────────────────────────────────────┘
│ └──────────────┘ │
│ (24 bytes: 3×8) │
└──────────────────┘
Estratégia de Crescimento do Vec
Quando o len atinge o capacity, o Vec precisa realocar. A estratégia padrão do Rust é
dobrar a capacidade:
Inserções sucessivas com push():
Estado 1: cap=1, len=1 [10]
Estado 2: cap=2, len=2 [10][20] ← realocou (cap 1→2)
Estado 3: cap=4, len=3 [10][20][30][ ] ← realocou (cap 2→4)
Estado 4: cap=4, len=4 [10][20][30][40]
Estado 5: cap=8, len=5 [10][20][30][40][50][ ][ ][ ] ← realocou (cap 4→8)
Custo amortizado de push: O(1)
Pior caso individual: O(n) quando precisa realocar e copiar tudo
Slices: &[T] e &mut [T]
Slices são visualizações (views) sobre uma sequência contígua de memória. Elas não possuem os dados — apenas referenciam uma região de um array ou Vec:
let v = vec![10, 20, 30, 40, 50];
let fatia = &v[1..4];
Vec<i32> Slice &[i32]
┌─────┐ ┌───┬───┬───┬───┬───┐ ┌─────────┐
│ ptr─┼────►│10 │20 │30 │40 │50 │◄──┼─┤ ptr │
│len=5│ └───┴─▲─┴───┴───┴───┘ │ ├─────────┤
│cap=5│ │ │ │ len = 3 │
└─────┘ └──────────────────┘ └─────────┘
aponta para v[1]
fatia = [20, 30, 40]
Complexidade
| Operação | Array [T; N] | Vec | Notas |
|---|---|---|---|
Acesso por índice v[i] | O(1) | O(1) | Cálculo direto de endereço |
| Busca (elemento arbitrário) | O(n) | O(n) | Busca linear; O(log n) se ordenado |
Inserção no final (push) | — | O(1) amortizado | O(n) no pior caso (realocação) |
Remoção do final (pop) | — | O(1) | Sem necessidade de mover elementos |
| Inserção no início/meio | O(n) | O(n) | Precisa deslocar elementos |
| Remoção do início/meio | O(n) | O(n) | Precisa deslocar elementos |
contains / busca linear | O(n) | O(n) | Percorre todos os elementos |
Ordenação (sort) | O(n log n) | O(n log n) | Usa padrão TimSort (estável) |
Implementação em Rust
Operações Básicas com Arrays
fn demonstrar_arrays() {
// Declaração de array com tipo e tamanho explícitos
let notas: [f64; 5] = [7.5, 8.0, 9.2, 6.8, 7.0];
// Array inicializado com valor padrão (todos iguais)
let zeros: [i32; 10] = [0; 10];
// Acesso por índice (verificado em tempo de execução)
let primeira_nota = notas[0];
println!("Primeira nota: {}", primeira_nota);
// Iteração com enumerate
for (i, nota) in notas.iter().enumerate() {
println!("Aluno {}: {:.1}", i + 1, nota);
}
// Tamanho do array
println!("Total de alunos: {}", notas.len());
// Slicing — criando uma fatia
let tres_primeiras = ¬as[0..3];
println!("Três primeiras notas: {:?}", tres_primeiras);
// Arrays suportam pattern matching
match notas {
[primeiro, .., ultimo] => {
println!("Primeira: {}, Última: {}", primeiro, ultimo);
}
}
// Média usando iteradores
let soma: f64 = notas.iter().sum();
let media = soma / notas.len() as f64;
println!("Média da turma: {:.2}", media);
println!("Zeros: {:?}", zeros);
}
Operações com Vec
fn demonstrar_vec() {
// Criação com macro vec!
let mut frutas = vec!["maçã", "banana", "laranja"];
// Criação com capacidade pré-alocada (evita realocações)
let mut numeros: Vec<i32> = Vec::with_capacity(100);
println!("Len: {}, Capacidade: {}", numeros.len(), numeros.capacity());
// Inserção no final — O(1) amortizado
numeros.push(10);
numeros.push(20);
numeros.push(30);
// Remoção do final — O(1)
if let Some(ultimo) = numeros.pop() {
println!("Removido: {}", ultimo); // 30
}
// Inserção em posição específica — O(n)
frutas.insert(1, "manga"); // ["maçã", "manga", "banana", "laranja"]
// Remoção por índice — O(n)
let removida = frutas.remove(2); // remove "banana"
println!("Fruta removida: {}", removida);
// swap_remove — O(1), mas não preserva ordem
frutas.push("uva");
frutas.push("pêra");
println!("Antes do swap_remove: {:?}", frutas);
frutas.swap_remove(0); // troca o primeiro pelo último e remove
println!("Após swap_remove(0): {:?}", frutas);
// Busca
if frutas.contains(&"manga") {
println!("Temos manga!");
}
// Encontrar posição
if let Some(pos) = frutas.iter().position(|&f| f == "uva") {
println!("Uva está na posição: {}", pos);
}
// Ordenação
let mut valores = vec![42, 17, 93, 5, 28, 61];
valores.sort(); // ordenação estável
println!("Ordenado: {:?}", valores);
// Deduplicação (requer ordenação prévia)
let mut com_duplicatas = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
com_duplicatas.sort();
com_duplicatas.dedup();
println!("Sem duplicatas: {:?}", com_duplicatas);
// Redimensionamento
let mut buffer = vec![0u8; 10];
buffer.resize(20, 0xFF); // expande preenchendo com 0xFF
println!("Buffer: {:?}", buffer);
}
Padrões com Iteradores
fn demonstrar_iteradores() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map — transformar cada elemento
let dobrados: Vec<i32> = numeros.iter().map(|&x| x * 2).collect();
println!("Dobrados: {:?}", dobrados);
// filter — selecionar elementos
let pares: Vec<&i32> = numeros.iter().filter(|&&x| x % 2 == 0).collect();
println!("Pares: {:?}", pares);
// fold — reduzir a um único valor (soma)
let soma = numeros.iter().fold(0, |acumulador, &x| acumulador + x);
println!("Soma: {}", soma);
// Encadear operações: filtrar pares, dobrar e somar
let resultado: i32 = numeros
.iter()
.filter(|&&x| x % 2 == 0) // [2, 4, 6, 8, 10]
.map(|&x| x * 2) // [4, 8, 12, 16, 20]
.sum(); // 60
println!("Pares dobrados somados: {}", resultado);
// chunks — dividir em pedaços
let dados = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
for (i, pedaco) in dados.chunks(3).enumerate() {
println!("Lote {}: {:?}", i, pedaco);
}
// windows — janelas deslizantes
let precos = vec![100, 102, 98, 105, 110, 108];
println!("Médias móveis (janela 3):");
for janela in precos.windows(3) {
let media: f64 = janela.iter().sum::<i32>() as f64 / 3.0;
println!(" {:?} → média = {:.1}", janela, media);
}
// flat_map — achatar estruturas aninhadas
let frases = vec!["olá mundo", "rust é incrível"];
let palavras: Vec<&str> = frases.iter().flat_map(|f| f.split_whitespace()).collect();
println!("Palavras: {:?}", palavras);
// zip — combinar dois iteradores
let nomes = vec!["Ana", "Bruno", "Clara"];
let idades = vec![25, 30, 28];
let pessoas: Vec<(&str, &i32)> = nomes.iter().zip(idades.iter()).collect();
println!("Pessoas: {:?}", pessoas);
// any e all — predicados
let tem_negativo = numeros.iter().any(|&x| x < 0);
let todos_positivos = numeros.iter().all(|&x| x > 0);
println!("Tem negativo? {} | Todos positivos? {}", tem_negativo, todos_positivos);
}
Métodos Principais
Array [T; N]
| Método / Operação | Descrição |
|---|---|
len() | Retorna o número de elementos (sempre N) |
iter() | Retorna um iterador sobre referências |
iter_mut() | Retorna um iterador mutável |
contains(&val) | Verifica se o valor está presente |
sort() | Ordena os elementos in-place |
reverse() | Inverte a ordem dos elementos |
[i..j] | Cria uma slice (fatia) do array |
map(f) | Aplica uma função a cada elemento (Rust 1.55+) |
Vec
| Método | Descrição |
|---|---|
Vec::new() | Cria um vetor vazio |
Vec::with_capacity(n) | Cria com capacidade pré-alocada |
push(val) | Adiciona ao final — O(1) amortizado |
pop() | Remove do final — O(1), retorna Option<T> |
insert(i, val) | Insere na posição i — O(n) |
remove(i) | Remove da posição i — O(n) |
swap_remove(i) | Remove trocando pelo último — O(1) |
retain(f) | Mantém apenas elementos que satisfazem f |
truncate(n) | Reduz o tamanho para n elementos |
drain(range) | Remove e retorna elementos do intervalo |
split_off(i) | Divide o vetor na posição i |
extend(iter) | Adiciona elementos de um iterador |
shrink_to_fit() | Reduz a capacidade ao tamanho atual |
Exemplo Prático: Pipeline de Processamento de Dados CSV
Vamos implementar um sistema realista que lê dados de vendas em formato CSV, processa com iteradores e gera relatórios agregados.
use std::fmt;
/// Representa um registro de venda
#[derive(Debug, Clone)]
struct Venda {
data: String,
produto: String,
categoria: String,
quantidade: u32,
preco_unitario: f64,
regiao: String,
}
impl Venda {
/// Calcula o valor total da venda
fn valor_total(&self) -> f64 {
self.quantidade as f64 * self.preco_unitario
}
}
/// Relatório de vendas por categoria
#[derive(Debug)]
struct RelatorioCategoria {
categoria: String,
total_vendas: f64,
quantidade_registros: usize,
ticket_medio: f64,
}
impl fmt::Display for RelatorioCategoria {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"| {:<15} | R$ {:>10.2} | {:>6} registros | Ticket médio: R$ {:>8.2} |",
self.categoria, self.total_vendas, self.quantidade_registros, self.ticket_medio
)
}
}
/// Analisa uma linha CSV e retorna uma Venda
fn parsear_linha_csv(linha: &str) -> Option<Venda> {
let campos: Vec<&str> = linha.split(',').map(|c| c.trim()).collect();
if campos.len() != 6 {
return None;
}
Some(Venda {
data: campos[0].to_string(),
produto: campos[1].to_string(),
categoria: campos[2].to_string(),
quantidade: campos[3].parse().ok()?,
preco_unitario: campos[4].parse().ok()?,
regiao: campos[5].to_string(),
})
}
/// Gera relatório agrupado por categoria usando apenas Vec e iteradores
fn gerar_relatorio_por_categoria(vendas: &[Venda]) -> Vec<RelatorioCategoria> {
// Coleta todas as categorias únicas
let mut categorias: Vec<String> = vendas.iter().map(|v| v.categoria.clone()).collect();
categorias.sort();
categorias.dedup();
// Para cada categoria, calcula as métricas
categorias
.into_iter()
.map(|cat| {
let vendas_categoria: Vec<&Venda> =
vendas.iter().filter(|v| v.categoria == cat).collect();
let total: f64 = vendas_categoria.iter().map(|v| v.valor_total()).sum();
let qtd = vendas_categoria.len();
let ticket = if qtd > 0 { total / qtd as f64 } else { 0.0 };
RelatorioCategoria {
categoria: cat,
total_vendas: total,
quantidade_registros: qtd,
ticket_medio: ticket,
}
})
.collect()
}
/// Encontra os N produtos mais vendidos (por valor total)
fn top_produtos(vendas: &[Venda], n: usize) -> Vec<(String, f64)> {
// Agrupa por produto
let mut produtos: Vec<String> = vendas.iter().map(|v| v.produto.clone()).collect();
produtos.sort();
produtos.dedup();
let mut ranking: Vec<(String, f64)> = produtos
.into_iter()
.map(|prod| {
let total: f64 = vendas
.iter()
.filter(|v| v.produto == prod)
.map(|v| v.valor_total())
.sum();
(prod, total)
})
.collect();
// Ordena por valor total decrescente
ranking.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
// Retorna apenas os N primeiros
ranking.into_iter().take(n).collect()
}
fn main() {
// Simulando dados CSV (em produção, viria de um arquivo)
let dados_csv = vec![
"2024-01-15, Notebook Dell, Eletrônicos, 2, 4500.00, Sudeste",
"2024-01-15, Cadeira Gamer, Móveis, 5, 1200.00, Sul",
"2024-01-16, Mouse Logitech, Eletrônicos, 15, 150.00, Sudeste",
"2024-01-16, Mesa Escritório, Móveis, 3, 800.00, Nordeste",
"2024-01-17, Monitor 27pol, Eletrônicos, 8, 2200.00, Sudeste",
"2024-01-17, Teclado Mecânico, Eletrônicos, 20, 350.00, Sul",
"2024-01-18, Sofá 3 Lugares, Móveis, 2, 3500.00, Sudeste",
"2024-01-18, Webcam HD, Eletrônicos, 12, 280.00, Centro-Oeste",
"2024-01-19, Estante Livros, Móveis, 4, 650.00, Nordeste",
"2024-01-19, SSD 1TB, Eletrônicos, 25, 420.00, Sudeste",
"2024-01-20, Cadeira Gamer, Móveis, 3, 1200.00, Norte",
"2024-01-20, Notebook Dell, Eletrônicos, 1, 4500.00, Nordeste",
];
// Pipeline de processamento usando iteradores
let vendas: Vec<Venda> = dados_csv
.iter()
.filter_map(|linha| parsear_linha_csv(linha))
.collect();
println!("══════════════════════════════════════════════════════");
println!(" RELATÓRIO DE VENDAS - JANEIRO 2024 ");
println!("══════════════════════════════════════════════════════\n");
// Resumo geral
let total_geral: f64 = vendas.iter().map(|v| v.valor_total()).sum();
let total_itens: u32 = vendas.iter().map(|v| v.quantidade).sum();
println!("Total de registros: {}", vendas.len());
println!("Total de itens vendidos: {}", total_itens);
println!("Valor total: R$ {:.2}\n", total_geral);
// Relatório por categoria
println!("─── Por Categoria ───────────────────────────────────");
let relatorio = gerar_relatorio_por_categoria(&vendas);
for r in &relatorio {
println!("{}", r);
}
// Top 5 produtos
println!("\n─── Top 5 Produtos ──────────────────────────────────");
for (i, (produto, valor)) in top_produtos(&vendas, 5).iter().enumerate() {
println!(" {}º {:<20} R$ {:.2}", i + 1, produto, valor);
}
// Vendas por região usando chunks para exibição paginada
println!("\n─── Vendas por Região (paginado) ────────────────────");
let vendas_por_pagina = 4;
for (pagina, grupo) in vendas.chunks(vendas_por_pagina).enumerate() {
println!(" Página {}:", pagina + 1);
for v in grupo {
println!(
" {} | {:<20} | {} un. | R$ {:.2}",
v.data, v.produto, v.quantidade, v.valor_total()
);
}
}
// Filtro avançado: vendas acima de R$ 5000 na região Sudeste
println!("\n─── Vendas > R$ 5000 no Sudeste ─────────────────────");
let grandes_vendas_sudeste: Vec<&Venda> = vendas
.iter()
.filter(|v| v.regiao == "Sudeste" && v.valor_total() > 5000.0)
.collect();
if grandes_vendas_sudeste.is_empty() {
println!(" Nenhuma venda encontrada com esses critérios.");
} else {
for v in &grandes_vendas_sudeste {
println!(
" {} — {} — R$ {:.2}",
v.data, v.produto, v.valor_total()
);
}
}
}
Comparação com Outras Estruturas
| Critério | Array/Vec | LinkedList | VecDeque |
|---|---|---|---|
| Acesso por índice | O(1) | O(n) | O(1) |
| Inserção no final | O(1) amortizado | O(1) | O(1) amortizado |
| Inserção no início | O(n) | O(1) | O(1) amortizado |
| Inserção no meio | O(n) | O(1)* | O(n) |
| Localidade de cache | Excelente | Ruim | Boa |
| Uso de memória | Compacto | Alto (ponteiros) | Compacto |
| Flexibilidade de tamanho | Vec: sim, Array: não | Sim | Sim |
*A inserção no meio de uma LinkedList é O(1) se já temos o ponteiro. Encontrar a posição ainda é O(n).
Quando usar Array/Vec?
- Use array
[T; N]quando o tamanho é conhecido em tempo de compilação e é relativamente pequeno (cabe na stack). - Use
Vec<T>na grande maioria dos casos — é a estrutura padrão para coleções dinâmicas. - Prefira
VecsobreLinkedListquase sempre: a localidade de cache compensa a complexidade teórica pior de inserções. - Use
Vec::with_capacity(n)quando souber o tamanho aproximado — evita realocações desnecessárias. - Considere
VecDequeapenas se precisa de inserções/remoções eficientes em ambas as extremidades.
Exercícios
Exercício 1: Estatísticas de um Vetor
Implemente uma função estatisticas(dados: &[f64]) -> (f64, f64, f64) que retorna a média,
a mediana e o desvio padrão de um vetor de números. Não utilize bibliotecas externas.
Dica: Para a mediana, lembre-se de ordenar uma cópia do vetor. Para vetores de tamanho par, a mediana é a média dos dois valores centrais.
Exercício 2: Merge de Vetores Ordenados
Implemente uma função merge_ordenado(a: &[i32], b: &[i32]) -> Vec<i32> que recebe dois vetores
já ordenados e retorna um novo vetor ordenado contendo todos os elementos de ambos. A complexidade
deve ser O(n + m), onde n e m são os tamanhos dos vetores de entrada.
Exercício 3: Rotação de Vetor
Implemente uma função rotacionar(v: &mut Vec<i32>, k: usize) que rotaciona o vetor k posições
para a direita in-place. Por exemplo: [1, 2, 3, 4, 5] rotacionado 2 posições resulta em
[4, 5, 1, 2, 3]. Tente fazer com complexidade O(n) de tempo e O(1) de espaço adicional,
utilizando a técnica de três reversões.
Conclusão
Arrays e Vec<T> são os alicerces da programação em Rust. Sua memória contígua oferece desempenho
excepcional para a maioria dos cenários, e o sistema de ownership do Rust garante segurança de
memória sem custo em tempo de execução.
Pontos-chave a lembrar:
- Arrays são para tamanhos fixos e conhecidos em compilação; vivem na stack.
- Vec é a escolha padrão para coleções dinâmicas — use
with_capacityquando possível. - Slices (
&[T]) são a forma idiomática de passar sequências para funções. - Iteradores são a forma preferida de processar dados — são preguiçosos, seguros e eficientes.
- A localidade de cache faz com que Vec supere LinkedList na prática, mesmo em cenários onde a teoria favorece listas ligadas.
Domine essas estruturas e você terá uma base sólida para todas as outras estruturas de dados em Rust.