Rust Admin

Guia Completo de Iteradores Customizados: Implementando o Trait Iterator Já leu

Por que Implementar Iteradores Customizados? Iteradores são fundamentais em linguagens funcionais e modernas como Rust, Python e JavaScript. Em Rust, o trait permite criar loops eficientes e expressivos sobre estruturas de dados personalizadas. Quando você implementa o trait , seu tipo se integra perfeitamente com o ecossistema Rust, funcionando com , métodos como , e . Isso não é apenas conveniência sintática — é performance garantida com zero-cost abstractions. Neste artigo, vamos construir iteradores reais, entender o trait 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 em Rust é simples mas poderoso. Ele requer apenas um método obrigatório: , que retorna . O tipo associado define o tipo de elemento que o iterador produz. > A elegância aqui: todos os métodos convenientes derivam de . Implementar um método bem feito oferece dezenas de operações de graça. Implementação Básica

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 intacta
  • iter_mut(): retorna referências mutáveis, permite modificação
  • into_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:

  1. Integração ecosistema: seu tipo funciona com for, map(), filter(), collect() — toda a linguagem passa a trabalhar com seus dados naturalmente.

  2. Eficiência garantida: lazy evaluation, zero-copy quando apropriado, e compilação otimizada pelo LLVM garantem performance em produção.

  3. 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.

Referências


Artigos relacionados