Iterator Trait em Rust

Guia completo do trait Iterator em Rust: map, filter, fold, collect, enumerate, zip, chain e criação de iteradores customizados.

O que é o Trait Iterator

O trait Iterator é a base da programação funcional e do processamento de coleções em Rust. Qualquer tipo que implemente Iterator precisa definir apenas um método: next(), que retorna Option<Self::Item>Some(valor) para o próximo elemento ou None quando a iteração termina. A partir disso, dezenas de métodos adaptadores ficam disponíveis automaticamente.

Iteradores em Rust são preguiçosos (lazy): adaptadores como map e filter não fazem nada até que um consumidor como collect, for_each, sum ou fold solicite os valores. Essa abordagem elimina alocações intermediárias e permite ao compilador otimizar cadeias inteiras de transformações em loops eficientes — frequentemente com desempenho igual ou superior ao de loops manuais.


Métodos Mais Usados

fn main() {
    let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // iter() - referências imutáveis
    // into_iter() - consome a coleção
    // iter_mut() - referências mutáveis

    // Demonstração básica com next()
    let mut iter = numeros.iter();
    assert_eq!(iter.next(), Some(&1));
    assert_eq!(iter.next(), Some(&2));

    println!("{numeros:?}"); // numeros ainda existe (iter() empresta)
}

Tabela de Métodos Principais

Adaptadores (preguiçosos — retornam outro iterador)

MétodoDescriçãoExemplo
map(f)Transforma cada elemento.map(|x| x * 2)
filter(f)Mantém elementos onde f é true.filter(|x| **x > 3)
filter_map(f)map + filter (descarta None).filter_map(|x| x.parse().ok())
flat_map(f)map + flatten.flat_map(|x| vec![x, x*10])
flatten()Achata iterador de iteradoresvec![vec![1,2], vec![3]].into_iter().flatten()
enumerate()Adiciona índice (i, val).enumerate()
zip(outro)Combina dois iteradores em tuplas.zip(outro_iter)
chain(outro)Concatena dois iteradores.chain(outro_iter)
take(n)Pega os primeiros n.take(5)
skip(n)Pula os primeiros n.skip(2)
take_while(f)Pega enquanto f for true.take_while(|x| **x < 5)
skip_while(f)Pula enquanto f for true.skip_while(|x| **x < 5)
peekable()Permite espiar o próximo sem consumir.peekable()
rev()Inverte (requer DoubleEndedIterator).rev()
cloned()Clona referências &TT.cloned()
copied()Copia referências &TT (Copy).copied()
inspect(f)Executa side-effect sem alterar.inspect(|x| println!("{x}"))
step_by(n)Avança n a cada iteração.step_by(2)

Consumidores (executam a iteração)

MétodoDescriçãoExemplo
collect()Coleta em uma coleção.collect::<Vec<_>>()
for_each(f)Executa f para cada elemento.for_each(|x| println!("{x}"))
fold(init, f)Reduz a um único valor.fold(0, |acc, x| acc + x)
reduce(f)Como fold, sem valor inicial.reduce(|a, b| a + b)
sum()Soma todos os elementos.sum::<i32>()
product()Produto de todos os elementos.product::<i32>()
count()Conta os elementos.count()
any(f)Algum satisfaz f?.any(|x| *x > 5)
all(f)Todos satisfazem f?.all(|x| *x > 0)
find(f)Primeiro que satisfaz f.find(|x| **x > 5)Option
position(f)Índice do primeiro que satisfaz f.position(|x| *x > 5)Option
min() / max()Menor / maior elemento.max()Option
min_by_key(f)Menor por critério.min_by_key(|x| x.abs())
last()Último elemento.last()Option
nth(n)N-ésimo elemento.nth(3)Option
unzip()Separa tuplas em duas coleções.unzip::<Vec<_>, Vec<_>>()

Exemplos Práticos

1. Pipeline de Transformação de Dados

#[derive(Debug)]
struct Venda {
    produto: String,
    valor: f64,
    quantidade: u32,
}

fn main() {
    let vendas = vec![
        Venda { produto: "Notebook".into(), valor: 3500.0, quantidade: 2 },
        Venda { produto: "Mouse".into(), valor: 89.90, quantidade: 15 },
        Venda { produto: "Teclado".into(), valor: 199.90, quantidade: 8 },
        Venda { produto: "Monitor".into(), valor: 1200.0, quantidade: 3 },
        Venda { produto: "Webcam".into(), valor: 149.90, quantidade: 5 },
    ];

    // Total de vendas acima de R$ 1000 em receita
    let total_alto_valor: f64 = vendas
        .iter()
        .map(|v| v.valor * v.quantidade as f64)
        .filter(|&receita| receita > 1000.0)
        .sum();
    println!("Receita alto valor: R$ {total_alto_valor:.2}");

    // Top 3 produtos por receita
    let mut por_receita: Vec<_> = vendas
        .iter()
        .map(|v| (&v.produto, v.valor * v.quantidade as f64))
        .collect();
    por_receita.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());

    println!("\nTop 3 por receita:");
    for (produto, receita) in por_receita.iter().take(3) {
        println!("  {produto}: R$ {receita:.2}");
    }
}

2. Criando um Iterador Customizado

struct Fibonacci {
    a: u64,
    b: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { a: 0, b: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let resultado = self.a;
        let novo = self.a.checked_add(self.b)?; // None se houver overflow
        self.a = self.b;
        self.b = novo;
        Some(resultado)
    }
}

fn main() {
    // Primeiros 15 números de Fibonacci
    let primeiros: Vec<u64> = Fibonacci::new().take(15).collect();
    println!("Fibonacci: {primeiros:?}");

    // Fibonacci menores que 1000
    let menores: Vec<u64> = Fibonacci::new()
        .take_while(|&n| n < 1000)
        .collect();
    println!("Menores que 1000: {menores:?}");

    // Soma dos pares de Fibonacci até 4 milhões
    let soma_pares: u64 = Fibonacci::new()
        .take_while(|&n| n <= 4_000_000)
        .filter(|n| n % 2 == 0)
        .sum();
    println!("Soma dos pares até 4M: {soma_pares}");
}

3. enumerate, zip e chain

fn main() {
    let linguagens = vec!["Rust", "Python", "Go", "TypeScript"];
    let notas = vec![10, 8, 7, 9];

    // enumerate — índice + valor
    println!("Rankings:");
    for (i, lang) in linguagens.iter().enumerate() {
        println!("  {}. {lang}", i + 1);
    }

    // zip — combinar dois iteradores
    println!("\nNotas:");
    for (lang, nota) in linguagens.iter().zip(notas.iter()) {
        println!("  {lang}: {nota}/10");
    }

    // chain — concatenar iteradores
    let frontend = vec!["React", "Vue"];
    let backend = vec!["Actix", "Axum"];
    let todos: Vec<&str> = frontend.iter().copied()
        .chain(backend.iter().copied())
        .collect();
    println!("\nFrameworks: {todos:?}");

    // unzip — separar tuplas
    let pares = vec![(1, "um"), (2, "dois"), (3, "três")];
    let (numeros, nomes): (Vec<i32>, Vec<&str>) = pares.into_iter().unzip();
    println!("\nNúmeros: {numeros:?}");
    println!("Nomes: {nomes:?}");
}

4. fold para Agregações Complexas

fn main() {
    let transacoes = vec![100.0, -50.0, 200.0, -30.0, -80.0, 150.0];

    // Calcular saldo, total de créditos e débitos em uma passada
    let (saldo, creditos, debitos) = transacoes.iter().fold(
        (0.0_f64, 0.0_f64, 0.0_f64),
        |(saldo, cred, deb), &valor| {
            if valor >= 0.0 {
                (saldo + valor, cred + valor, deb)
            } else {
                (saldo + valor, cred, deb + valor.abs())
            }
        },
    );

    println!("Saldo: R$ {saldo:.2}");
    println!("Total créditos: R$ {creditos:.2}");
    println!("Total débitos: R$ {debitos:.2}");

    // Construir uma string formatada com fold
    let palavras = vec!["Rust", "é", "incrível"];
    let frase = palavras.iter().enumerate().fold(
        String::new(),
        |mut acc, (i, palavra)| {
            if i > 0 {
                acc.push(' ');
            }
            acc.push_str(palavra);
            acc
        },
    );
    println!("{frase}");
}

5. windows e chunks para Processamento em Blocos

fn main() {
    let precos = vec![100.0, 102.0, 98.0, 105.0, 110.0, 108.0, 115.0, 112.0];

    // Média móvel de 3 períodos usando windows
    let medias_moveis: Vec<f64> = precos
        .windows(3)
        .map(|janela| janela.iter().sum::<f64>() / janela.len() as f64)
        .collect();
    println!("Médias móveis: {medias_moveis:.1?}");

    // Variação percentual entre dias consecutivos
    let variacoes: Vec<f64> = precos
        .windows(2)
        .map(|par| ((par[1] - par[0]) / par[0]) * 100.0)
        .collect();
    println!("Variações: {variacoes:.2?}%");

    // Processar dados em lotes de 3
    let dados: Vec<i32> = (1..=12).collect();
    let lotes: Vec<Vec<i32>> = dados
        .chunks(3)
        .map(|chunk| chunk.to_vec())
        .collect();
    println!("Lotes de 3: {lotes:?}");
}

Dicas de Performance e Armadilhas

  1. Iteradores são zero-cost: O compilador otimiza cadeias de iteradores para loops simples. Não há alocação intermediária entre map, filter, etc. Use sem medo.

  2. collect() precisa de tipo: O compilador nem sempre consegue inferir o tipo destino. Use turbofish: .collect::<Vec<_>>() ou anote a variável: let v: Vec<_> = ....

  3. iter() vs into_iter(): iter() empresta a coleção (retorna &T), into_iter() consome (retorna T). Se precisar dos valores, use into_iter() ou .iter().copied() para tipos Copy.

  4. Cuidado com filter em referências: O predicado de filter recebe &&T quando usado com iter(). Use |&&x| ou |x| **x > 5 para desreferenciar.

  5. for_each vs for: Ambos são equivalentes em performance. Use for_each em cadeias de iteradores; use for quando precisar de break, continue ou return.

  6. peekable() para look-ahead: Quando precisar decidir com base no próximo elemento sem consumi-lo, use .peekable() e o método peek().


Veja Também