O Iterator Pattern (Padrão de Iterador) permite percorrer elementos de uma coleção sem expor sua representação interna. Em Rust, este padrão não é apenas um design pattern — é uma peça central da linguagem, integrado profundamente na biblioteca padrão e no compilador.
A trait Iterator de Rust vai muito além do que GoF imaginava: ela oferece avaliação lazy (preguiçosa), dezenas de combinadores composáveis (map, filter, fold, collect), e graças a monomorphization, iteradores em Rust compilam para código tão eficiente quanto loops manuais — são uma verdadeira abstração de custo zero.
Problema
Ao trabalhar com coleções de dados, surgem desafios recorrentes:
- Como percorrer diferentes estruturas de dados com uma interface uniforme?
- Como compor transformações sem criar coleções intermediárias?
- Como processar grandes conjuntos de dados sem carregar tudo na memória?
- Como permitir que o consumidor controle o ritmo da iteração?
Sem iteradores, cada estrutura de dados precisaria de sua própria API de travessia, e composição de operações exigiria vetores temporários para cada passo intermediário.
Solução em Rust
A Trait Iterator
O coração do padrão em Rust é a trait Iterator:
// Definição simplificada da trait Iterator da biblioteca padrão
trait Iterator {
type Item; // Tipo dos elementos produzidos
// Único método obrigatório
fn next(&mut self) -> Option<Self::Item>;
// Dezenas de métodos fornecidos automaticamente:
// map, filter, fold, collect, enumerate, zip, take, skip...
}
Implementando um Iterador Customizado
Vamos criar um iterador que gera a sequência de Fibonacci:
/// Iterador que gera números de Fibonacci infinitamente
struct Fibonacci {
atual: u64,
proximo: u64,
}
impl Fibonacci {
fn novo() -> Self {
Fibonacci {
atual: 0,
proximo: 1,
}
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let resultado = self.atual;
self.atual = self.proximo;
self.proximo = resultado + self.proximo;
Some(resultado) // Nunca retorna None — é infinito!
}
}
fn main() {
// Pegar os 10 primeiros números de Fibonacci
let fibs: Vec<u64> = Fibonacci::novo().take(10).collect();
println!("Fibonacci: {:?}", fibs);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// Soma dos Fibonacci menores que 1000
let soma: u64 = Fibonacci::novo()
.take_while(|&n| n < 1000)
.sum();
println!("Soma dos Fibonacci < 1000: {}", soma);
// Fibonacci pares menores que 4_000_000
let soma_pares: u64 = Fibonacci::novo()
.take_while(|&n| n < 4_000_000)
.filter(|n| n % 2 == 0)
.sum();
println!("Soma dos Fibonacci pares < 4M: {}", soma_pares);
}
Combinadores Essenciais
fn demonstrar_combinadores() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map: transforma cada elemento
let dobrados: Vec<i32> = numeros.iter().map(|n| n * 2).collect();
println!("Dobrados: {:?}", dobrados);
// filter: seleciona elementos que satisfazem um predicado
let pares: Vec<&i32> = numeros.iter().filter(|n| *n % 2 == 0).collect();
println!("Pares: {:?}", pares);
// fold: acumula todos os elementos em um único valor
let soma = numeros.iter().fold(0, |acumulador, &n| acumulador + n);
println!("Soma: {}", soma);
// enumerate: adiciona índice a cada elemento
for (indice, valor) in numeros.iter().enumerate() {
if indice < 3 {
println!(" [{}] = {}", indice, valor);
}
}
// zip: combina dois iteradores em pares
let nomes = vec!["Alice", "Bob", "Carol"];
let idades = vec![30, 25, 28];
let pessoas: Vec<_> = nomes.iter().zip(idades.iter()).collect();
println!("Pessoas: {:?}", pessoas);
// chain: concatena dois iteradores
let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let todos: Vec<&i32> = a.iter().chain(b.iter()).collect();
println!("Encadeados: {:?}", todos);
// flat_map: mapeia e achata resultados
let frases = vec!["olá mundo", "rust é incrível"];
let palavras: Vec<&str> = frases.iter().flat_map(|f| f.split_whitespace()).collect();
println!("Palavras: {:?}", palavras);
// windows e chunks (em slices)
let dados = [1, 2, 3, 4, 5];
let medias_moveis: Vec<f64> = dados.windows(3)
.map(|janela| janela.iter().sum::<i32>() as f64 / 3.0)
.collect();
println!("Médias móveis: {:?}", medias_moveis);
}
A Trait IntoIterator
A trait IntoIterator permite que qualquer tipo seja usado em um loop for:
/// Coleção customizada que implementa IntoIterator
struct ListaTarefas {
tarefas: Vec<String>,
}
impl ListaTarefas {
fn nova() -> Self {
ListaTarefas { tarefas: Vec::new() }
}
fn adicionar(&mut self, tarefa: &str) {
self.tarefas.push(tarefa.to_string());
}
}
/// Permite iterar consumindo a lista
impl IntoIterator for ListaTarefas {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.tarefas.into_iter()
}
}
/// Permite iterar por referência
impl<'a> IntoIterator for &'a ListaTarefas {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.tarefas.iter()
}
}
fn main() {
let mut lista = ListaTarefas::nova();
lista.adicionar("Estudar Rust");
lista.adicionar("Escrever testes");
lista.adicionar("Fazer code review");
// Graças a IntoIterator, funciona no for
for tarefa in &lista {
println!("- {}", tarefa);
}
// Consumindo a lista
let todas: Vec<String> = lista.into_iter().collect();
println!("Total: {} tarefas", todas.len());
}
Diagrama
Avaliação Lazy — Nenhuma coleção intermediária é criada:
vec![1,2,3,4,5,6,7,8,9,10]
│
▼ .iter()
┌─────────────┐
│ Iter<i32> │ ── produz &i32 sob demanda
└──────┬──────┘
▼ .filter(|n| *n % 2 == 0)
┌─────────────┐
│ Filter │ ── pula ímpares, passa pares
└──────┬──────┘
▼ .map(|n| n * 10)
┌─────────────┐
│ Map │ ── multiplica por 10
└──────┬──────┘
▼ .take(3)
┌─────────────┐
│ Take │ ── para após 3 elementos
└──────┬──────┘
▼ .collect()
┌─────────────┐
│ Vec<i32> │ ── [20, 40, 60]
└─────────────┘
Apenas 6 chamadas a next() foram feitas no total!
Os elementos 8, 9, 10 nunca foram visitados.
Trait IntoIterator — Três formas de iterar:
┌──────────────┐ ┌─────────────────────┐
│ for x in v │ ──▶ │ v.into_iter() │ ── consome v
│ for x in &v │ ──▶ │ (&v).into_iter() │ ── empresta v
│ for x in &mut v│──▶│ (&mut v).into_iter()│ ── empresta mut
└──────────────┘ └─────────────────────┘
Exemplo do Mundo Real
Um iterador customizado para percorrer resultados paginados de um banco de dados:
use std::collections::VecDeque;
/// Representa um registro do banco de dados
#[derive(Debug, Clone)]
struct Registro {
id: u64,
nome: String,
valor: f64,
}
/// Simula uma conexão com banco de dados
struct ConexaoBD {
// Em produção, seria uma conexão real (diesel, sqlx, etc.)
dados: Vec<Registro>,
}
impl ConexaoBD {
fn nova_com_dados(dados: Vec<Registro>) -> Self {
ConexaoBD { dados }
}
/// Busca uma página de resultados (simulado)
fn buscar_pagina(&self, offset: usize, limite: usize) -> Vec<Registro> {
self.dados.iter()
.skip(offset)
.take(limite)
.cloned()
.collect()
}
fn total_registros(&self) -> usize {
self.dados.len()
}
}
/// Iterador que busca resultados do banco em páginas sob demanda
struct IteradorResultados<'a> {
conexao: &'a ConexaoBD,
tamanho_pagina: usize,
offset_atual: usize,
buffer: VecDeque<Registro>,
total: usize,
registros_entregues: usize,
}
impl<'a> IteradorResultados<'a> {
fn novo(conexao: &'a ConexaoBD, tamanho_pagina: usize) -> Self {
let total = conexao.total_registros();
IteradorResultados {
conexao,
tamanho_pagina,
offset_atual: 0,
buffer: VecDeque::new(),
total,
registros_entregues: 0,
}
}
/// Carrega a próxima página no buffer interno
fn carregar_proxima_pagina(&mut self) {
let pagina = self.conexao.buscar_pagina(
self.offset_atual,
self.tamanho_pagina,
);
self.offset_atual += pagina.len();
for registro in pagina {
self.buffer.push_back(registro);
}
}
}
impl<'a> Iterator for IteradorResultados<'a> {
type Item = Registro;
fn next(&mut self) -> Option<Self::Item> {
// Se o buffer está vazio e ainda há registros, carrega mais
if self.buffer.is_empty() && self.registros_entregues < self.total {
self.carregar_proxima_pagina();
}
// Retorna o próximo registro do buffer
let registro = self.buffer.pop_front()?;
self.registros_entregues += 1;
Some(registro)
}
// Dica de tamanho para otimizar collect()
fn size_hint(&self) -> (usize, Option<usize>) {
let restante = self.total - self.registros_entregues;
(restante, Some(restante))
}
}
/// Extensão da conexão para criar iteradores facilmente
impl ConexaoBD {
fn iterar(&self, tamanho_pagina: usize) -> IteradorResultados {
IteradorResultados::novo(self, tamanho_pagina)
}
}
fn main() {
// Criando dados de exemplo
let dados: Vec<Registro> = (1..=100)
.map(|i| Registro {
id: i,
nome: format!("Produto {}", i),
valor: i as f64 * 9.99,
})
.collect();
let bd = ConexaoBD::nova_com_dados(dados);
// Iterando com páginas de 10 registros
// O banco é consultado sob demanda!
let caros: Vec<Registro> = bd.iterar(10)
.filter(|r| r.valor > 500.0)
.take(5)
.collect();
println!("5 produtos caros (>500):");
for reg in &caros {
println!(" {} - {} - R${:.2}", reg.id, reg.nome, reg.valor);
}
// Cálculo de estatísticas com iteradores
let (total, soma) = bd.iterar(20)
.fold((0u64, 0.0f64), |(cnt, soma), r| (cnt + 1, soma + r.valor));
println!("\nTotal: {} registros, Soma: R${:.2}", total, soma);
println!("Média: R${:.2}", soma / total as f64);
}
Quando Usar
- Coleções customizadas: Qualquer estrutura de dados que contenha elementos
- Processamento em pipeline: Encadear transformações sem alocações intermediárias
- Dados paginados ou em streaming: Buscar dados sob demanda de fontes externas
- Sequências infinitas: Fibonacci, números aleatórios, leituras de sensor
- Redução de dados: Agregação com
fold,sum,count,min,max - Interoperabilidade:
IntoIteratorpermite que seus tipos funcionem emfor
Quando NÃO Usar
- Acesso aleatório necessário: Iteradores são sequenciais; use slices ou
IndexMut - Iteração bidirecional frequente:
DoubleEndedIteratorexiste, mas nem sempre é prático - Estado compartilhado complexo: Se cada passo depende de estado externo mutável, closures em combinadores ficam difíceis
- Depuração difícil: Cadeias longas de combinadores podem ser difíceis de depurar — quebre em etapas
Variações em Rust
DoubleEndedIterator
Iteradores que podem ser consumidos de ambas as extremidades:
fn demonstrar_dupla_ponta() {
let nums = vec![1, 2, 3, 4, 5];
// .rev() inverte a iteração
let invertido: Vec<&i32> = nums.iter().rev().collect();
println!("Invertido: {:?}", invertido);
// Processar das duas pontas simultaneamente
let mut iter = nums.iter();
println!("Frente: {:?}", iter.next()); // Some(1)
println!("Trás: {:?}", iter.next_back()); // Some(5)
println!("Frente: {:?}", iter.next()); // Some(2)
println!("Trás: {:?}", iter.next_back()); // Some(4)
}
ExactSizeIterator
Iteradores que sabem seu tamanho exato, otimizando collect:
struct Intervalo {
atual: i32,
fim: i32,
}
impl Iterator for Intervalo {
type Item = i32;
fn next(&mut self) -> Option<i32> {
if self.atual < self.fim {
let val = self.atual;
self.atual += 1;
Some(val)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let restante = (self.fim - self.atual) as usize;
(restante, Some(restante))
}
}
impl ExactSizeIterator for Intervalo {
fn len(&self) -> usize {
(self.fim - self.atual) as usize
}
}
Iteradores com Iterator::scan para estado acumulado
fn demonstrar_scan() {
// scan mantém estado entre iterações
let saldo_acumulado: Vec<f64> = vec![100.0, -30.0, 50.0, -10.0, 200.0]
.into_iter()
.scan(0.0f64, |saldo, transacao| {
*saldo += transacao;
Some(*saldo)
})
.collect();
println!("Saldo acumulado: {:?}", saldo_acumulado);
// [100.0, 70.0, 120.0, 110.0, 310.0]
}
Padrões Relacionados
- Visitor: Visitor percorre estruturas com duplo despacho; Iterator percorre sequencialmente
- Composite: Estruturas compostas frequentemente expõem iteradores para travessia
- Strategy: Combinadores como
mapefiltersão essencialmente strategies aplicadas a elementos - Builder: A cadeia de combinadores do iterador lembra o padrão builder
Conclusão
O Iterator Pattern em Rust transcende o padrão GoF original. A trait Iterator da biblioteca padrão, com seus mais de 70 métodos fornecidos automaticamente, a avaliação lazy que evita alocações intermediárias e a monomorphization que elimina overhead de abstração fazem dos iteradores Rust uma das ferramentas mais poderosas da linguagem.
Dominar iteradores é essencial para escrever código Rust idiomático. A combinação de map, filter, fold e collect cobre a maioria dos cenários de processamento de dados, e a implementação de Iterator e IntoIterator para tipos customizados é uma das habilidades mais valorizadas na comunidade Rust.