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