Iteradores: o trait Iterator
1. O que é um iterador em Rust?
Em Rust, um iterador é qualquer tipo que implementa o trait Iterator. Este trait é definido na biblioteca padrão e possui um método fundamental obrigatório: next. A assinatura básica é:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
O tipo associado Item define o tipo dos elementos produzidos pelo iterador. O método next retorna Some(Item) enquanto houver elementos, e None quando o iterador é exaurido.
Uma diferença crucial em Rust é que coleções (como Vec, HashMap) não são iteradores — elas podem ser convertidas em iteradores. Iteradores são lazy: nenhum elemento é processado até que um método consumidor force a iteração. Coleções, por outro lado, são estruturas de dados eager que armazenam todos os elementos imediatamente.
let v = vec![1, 2, 3];
let iter = v.iter(); // Nada é executado ainda
// Apenas quando chamamos next() ou usamos um consumidor
2. Implementando o trait Iterator manualmente
Vamos implementar um iterador personalizado que gera números pares até um limite:
struct Pares {
atual: i32,
max: i32,
}
impl Pares {
fn new(max: i32) -> Self {
Pares { atual: 0, max }
}
}
impl Iterator for Pares {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
self.atual += 2;
if self.atual <= self.max {
Some(self.atual)
} else {
None
}
}
}
fn main() {
let pares: Vec<i32> = Pares::new(10).collect();
println!("{:?}", pares); // [2, 4, 6, 8, 10]
}
3. Métodos consumidores (consuming adapters)
Métodos consumidores forçam a iteração completa ou parcial, consumindo o iterador no processo.
let nums = vec![1, 2, 3, 4, 5];
// collect: coleta todos os elementos em uma coleção
let quadrados: Vec<i32> = nums.iter().map(|x| x * x).collect();
// sum: soma todos os elementos
let total: i32 = nums.iter().sum();
println!("Soma: {}", total); // 15
// fold: acumula com valor inicial
let produto = nums.iter().fold(1, |acc, x| acc * x);
println!("Produto: {}", produto); // 120
// count: conta elementos
println!("Quantidade: {}", nums.iter().count()); // 5
// nth: acessa o enésimo elemento (sem consumir todos)
let terceiro = nums.iter().nth(2);
println!("Terceiro: {:?}", terceiro); // Some(3)
4. Métodos adaptadores (iterator adapters)
Adaptadores transformam um iterador em outro iterador, mantendo a lazy evaluation.
let nums = 1..=10;
// map: transforma cada elemento
let dobrados: Vec<i32> = nums.clone().map(|x| x * 2).collect();
// filter: mantém apenas elementos que satisfazem a condição
let pares: Vec<i32> = nums.clone().filter(|x| x % 2 == 0).collect();
// take: limita a quantidade de elementos
let primeiros_tres: Vec<i32> = nums.clone().take(3).collect();
// skip: pula elementos
let depois_do_quinto: Vec<i32> = nums.clone().skip(5).collect();
// chain: combina dois iteradores em sequência
let a = 1..=3;
let b = 4..=6;
let combinado: Vec<i32> = a.chain(b).collect();
// zip: combina dois iteradores em pares
let nomes = vec!["Ana", "Beto", "Carlos"];
let idades = vec![25, 30, 35];
let pessoas: Vec<(&str, i32)> = nomes.into_iter().zip(idades).collect();
5. Iteradores sobre referências e valores
Rust oferece três formas de iterar sobre coleções, cada uma com implicações de ownership:
let mut v = vec![1, 2, 3];
// iter(): itera por referência (&T) — não consome a coleção
for x in v.iter() {
println!("{}", x); // x: &i32
}
println!("v ainda é válido: {:?}", v);
// iter_mut(): itera por referência mutável (&mut T)
for x in v.iter_mut() {
*x *= 2;
}
println!("Modificado: {:?}", v);
// into_iter(): itera por valor (T) — consome a coleção
for x in v.into_iter() {
println!("{}", x); // x: i32 (ownership transferido)
}
// println!("{:?}", v); // Erro! v foi movido
O trait IntoIterator é o que permite o uso de for loops. Quando escrevemos for x in colecao, o compilador chama into_iter() automaticamente.
6. Iteradores infinitos e lazy evaluation
A lazy evaluation permite criar iteradores que teoricamente são infinitos:
use std::iter;
// repeat: repete o mesmo valor infinitamente
let infinito = iter::repeat(42);
let primeiros_5: Vec<i32> = infinito.take(5).collect();
println!("{:?}", primeiros_5); // [42, 42, 42, 42, 42]
// once: exatamente um elemento
let unico = iter::once("único");
// Cuidado: sem take(), isso seria um loop infinito
let mut contador = 0;
for x in iter::repeat("perigo") {
if contador >= 3 { break; }
println!("{}", x);
contador += 1;
}
7. Combinando iteradores com closures
Closures podem capturar variáveis do ambiente de diferentes formas:
let mut numeros = vec![1, 2, 3, 4, 5, 6];
let mut soma_pares = 0;
// Captura por referência mutável
numeros.iter()
.filter(|&&x| x % 2 == 0)
.for_each(|x| soma_pares += x);
println!("Soma dos pares: {}", soma_pares);
// Capturando por valor (move)
let limite = 3;
let menores: Vec<i32> = numeros.into_iter()
.filter(move |x| *x < limite)
.collect();
// Exemplo com estado mutável usando filter
let mut contador = 0;
let dados = vec![10, 20, 30, 40, 50];
let selecionados: Vec<i32> = dados.into_iter()
.filter(|_| {
contador += 1;
contador % 2 == 0 // pega apenas elementos em posições pares
})
.collect();
println!("{:?}", selecionados); // [20, 40]
8. Boas práticas e desempenho
Algumas recomendações para usar iteradores de forma eficiente:
let dados = vec![
vec![1, 2, 3],
vec![4, 5],
vec![6, 7, 8, 9],
];
// Prefira flat_map em vez de map + flatten
let achatado: Vec<i32> = dados.iter()
.flat_map(|v| v.iter())
.copied()
.collect();
// filter_map: combine filter e map em um único passo
let textos = vec!["1", "a", "2", "b", "3"];
let numeros: Vec<i32> = textos.iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
// Evite clone desnecessário em cadeias
let original = vec![1, 2, 3, 4, 5];
let resultado: Vec<i32> = original.iter()
.map(|x| x * 2)
.filter(|x| x > 5)
.collect(); // collect aloca apenas no final
Boas práticas resumidas:
- Use collect apenas no final da cadeia para alocar uma única vez
- Prefira flat_map em vez de map seguido de flatten
- Use filter_map para transformar e filtrar simultaneamente
- Evite clone dentro de closures — prefira copied() ou cloned() quando necessário
- Iteradores são zero-cost abstractions em Rust — usar map e filter não tem custo adicional em relação a loops manuais
Referências
- The Rust Programming Language - Iterators — Capítulo oficial do livro sobre iteradores e closures
- Rust Standard Library - Iterator trait — Documentação completa do trait Iterator com todos os métodos
- Rust by Example - Iterators — Exemplos práticos de implementação e uso de iteradores
- Rustnomicon - Iterators — Discussão avançada sobre implementação de iteradores seguros
- Effective Rust - Iterators — Guia de boas práticas para uso eficiente de iteradores em Rust