Por que Implementar Iteradores Customizados?
Iteradores são fundamentais em linguagens funcionais e modernas como Rust, Python e JavaScript. Em Rust, o trait Iterator permite criar loops eficientes e expressivos sobre estruturas de dados personalizadas. Quando você implementa o trait Iterator, seu tipo se integra perfeitamente com o ecossistema Rust, funcionando com for, métodos como map(), filter() e collect(). Isso não é apenas conveniência sintática — é performance garantida com zero-cost abstractions.
Neste artigo, vamos construir iteradores reais, entender o trait Iterator por dentro e aprender quando e como usá-los. Você sairá daqui sabendo implementar iteradores para suas próprias estruturas de dados.
Entendendo o Trait Iterator
A Anatomia do Trait
O trait Iterator em Rust é simples mas poderoso. Ele requer apenas um método obrigatório: next(), que retorna Option<Self::Item>. O tipo associado Item define o tipo de elemento que o iterador produz.
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Métodos padrão (map, filter, collect, etc.)
// todos construídos sobre next()
}
A elegância aqui: todos os métodos convenientes derivam de
next(). Implementar um método bem feito oferece dezenas de operações de graça.
Implementação Básica
Vamos criar um iterador para uma sequência de números pares:
struct ContadorPares {
inicio: u32,
fim: u32,
}
impl ContadorPares {
fn new(inicio: u32, fim: u32) -> Self {
ContadorPares { inicio, fim }
}
}
impl Iterator for ContadorPares {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.inicio < self.fim {
if self.inicio % 2 == 0 {
let resultado = self.inicio;
self.inicio += 1;
Some(resultado)
} else {
self.inicio += 1;
self.next() // recursão para pular ímpares
}
} else {
None
}
}
}
fn main() {
let contador = ContadorPares::new(0, 10);
for num in contador {
println!("{}", num); // 0, 2, 4, 6, 8
}
}
Note que o loop for funciona automaticamente. Rust reconhece que ContadorPares implementa Iterator e desaçúcar o loop para chamadas sucessivas de next().
Iteradores em Estruturas de Dados
Iterando Sobre Estruturas Personalizadas
Frequentemente você quer iterar sobre uma coleção própria. Considere uma lista ligada customizada:
struct No<T> {
valor: T,
proximo: Option<Box<No<T>>>,
}
struct ListaLigada<T> {
cabeca: Option<Box<No<T>>>,
}
struct IteradorLista<T> {
no_atual: Option<&'a Box<No<T>>>,
}
impl<T> ListaLigada<T> {
fn iter(&self) -> IteradorLista<T> {
IteradorLista {
no_atual: self.cabeca.as_ref(),
}
}
}
impl<'a, T> Iterator for IteradorLista<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.no_atual.map(|no| {
self.no_atual = no.proximo.as_ref();
&no.valor
})
}
}
fn main() {
let mut lista = ListaLigada { cabeca: None };
// Preenchimento da lista...
for valor in lista.iter() {
println!("{}", valor);
}
}
Aqui usamos lifetimes ('a) para garantir que as referências retornadas pelo iterador vivem o tempo adequado. O método map() em Option é elegante: se houver um nó, retorna seu valor e avança; caso contrário, retorna None automaticamente.
Iteradores Consumidores vs. Emprestados
Há três padrões comuns:
iter(): retorna referências (não-mutáveis), a coleção permanece intactaiter_mut(): retorna referências mutáveis, permite modificaçãointo_iter(): consome a coleção, transfere ownership
impl<T> ListaLigada<T> {
fn iter_mut(&mut self) -> IteradorListaMut<T> {
IteradorListaMut {
no_atual: self.cabeca.as_mut(),
}
}
}
impl<'a, T> Iterator for IteradorListaMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
self.no_atual.take().map(|no| {
self.no_atual = no.proximo.as_mut();
&mut no.valor
})
}
}
Operações Encadeadas e Performance
Composição de Métodos
Um dos superpoderes do trait Iterator é a capacidade de encadear operações de forma limpa e eficiente. Veja:
fn main() {
let pares = ContadorPares::new(0, 20)
.filter(|n| n % 3 != 0) // Remove múltiplos de 3
.map(|n| n * 2) // Dobra cada número
.take(5); // Pega apenas 5 primeiros
let resultado: Vec<u32> = pares.collect();
println!("{:?}", resultado);
}
Isso não aloca intermediários como vetores temporários — é lazy evaluation. O iterador computaciona valores sob demanda, de forma eficiente em memória e CPU.
Implementando Métodos Customizados
Você pode adicionar métodos específicos ao seu iterador:
trait IteratorExt: Iterator {
fn dobrar(self) -> Map<Self, fn(Self::Item) -> Self::Item>
where
Self: Sized,
{
self.map(|x: Self::Item| x * 2)
}
}
impl<I: Iterator> IteradorExt for I {}
// Uso:
let resultado: Vec<u32> = ContadorPares::new(1, 5)
.dobrar()
.collect();
Aqui estendemos qualquer iterador com novos comportamentos sem modificar sua definição — isso é composição horizontal em ação.
Conclusão
Implementar o trait Iterator em Rust oferece três ganhos centrais:
-
Integração ecosistema: seu tipo funciona com
for,map(),filter(),collect()— toda a linguagem passa a trabalhar com seus dados naturalmente. -
Eficiência garantida: lazy evaluation, zero-copy quando apropriado, e compilação otimizada pelo LLVM garantem performance em produção.
-
Clareza expressiva: iteradores customizados transformam lógica complexa de percurso em código declarativo e legível, reduzindo bugs.
O investimento em dominar Iterator se paga rapidamente. Toda estrutura de dados relevante em Rust implementa este trait — aprender a construir iteradores é aprender a linguagem na profundidade certa.