Da trilha de Rust Este conceito ainda não saiu como card do dia. ir para o card de hoje

Rust Coleções Intermediário

Iteradores

Em Rust, iteradores são uma abstração poderosa para processar sequências de valores. Eles permitem encadear operações como map, filter e collect de forma declarativa e eficiente. O trait Iterator define um método next que retorna Option<Item>, e a partir dele dezenas de adaptadores são implementados.

Uma característica fundamental é a preguiça (lazy evaluation): adaptadores como map e filter não fazem nada até que um método consumidor (como collect, sum ou for_each) seja chamado. Isso possibilita otimizações e evita alocações desnecessárias.

Além de usar iteradores prontos, você pode implementar seu próprio iterador criando um tipo que implemente o trait Iterator. Isso é útil para percorrer estruturas customizadas ou gerar sequências sob demanda.

Neste card, vamos explorar desde o uso básico de map, filter e collect, entender a preguiça, e por fim implementar um iterador personalizado.

Pontos-chave

  • O trait Iterator exige a implementação do método next, que retorna Option<Self::Item>.
  • map transforma cada elemento, filter seleciona elementos, e collect consome o iterador em uma coleção.
  • Iteradores são preguiçosos: adaptadores não executam nada até um consumidor ser chamado.
  • Você pode implementar Iterator para seus próprios tipos e ganhar todos os adaptadores de graça.
  • iter() empresta, iter_mut() empresta mutavelmente e into_iter() consome a coleção.
iterators.rs
fn main() {
    let numeros = vec![1, 2, 3, 4, 5, 6];

    // map + filter + collect: dobra os pares
    let dobrados_pares: Vec<i32> = numeros
        .iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .collect();
    println!("Pares dobrados: {:?}", dobrados_pares);

    // Iteradores são preguiçosos: nada acontece até consumir
    let mut iter = numeros.iter().map(|&x| {
        println!("Processando {}", x);
        x * 10
    });
    println!("Antes de consumir");
    let primeiro = iter.next(); // só aqui o primeiro é processado
    println!("Primeiro: {:?}", primeiro);

    // Implementando um iterador personalizado: contador até um limite
    struct ContadorAte {
        atual: u32,
        limite: u32,
    }

    impl Iterator for ContadorAte {
        type Item = u32;

        fn next(&mut self) -> Option<Self::Item> {
            if self.atual < self.limite {
                let valor = self.atual;
                self.atual += 1;
                Some(valor)
            } else {
                None
            }
        }
    }

    let contador = ContadorAte { atual: 0, limite: 3 };
    let valores: Vec<u32> = contador.collect();
    println!("Contador: {:?}", valores);
}

Exercícios

  1. 1
    Fácil

    Filtrar e dobrar números ímpares

    Dado o vetor numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], use iteradores para criar um novo vetor contendo apenas os números ímpares multiplicados por 3.

    Resolver
  2. 2
    Médio

    Iterador personalizado: números de Fibonacci

    Implemente um iterador Fibonacci que gera a sequência de Fibonacci infinita (0, 1, 1, 2, 3, 5, ...). Use-o para coletar os 10 primeiros números em um Vec<u64> e imprimi-los.

    Resolver
  3. 3
    Desafio

    Pipeline de processamento com iterador personalizado e adaptadores

    Crie um iterador Primos que gera números primos infinitamente. Em seguida, use uma cadeia de adaptadores para: pegar os primos, filtrar aqueles cujo quadrado é menor que 200, mapear para o quadrado, e coletar os resultados em um Vec<u64>. Imprima o vetor.

    Resolver

Continue estudando