Iteradores em Rust: Guia Completo | Rust Brasil

Guia de iteradores em Rust: map, filter, fold, collect, chain, zip e iteradores personalizados com trait Iterator.

Iteradores são o coração da programação funcional em Rust. Eles permitem processar sequências de dados de forma expressiva, componível e — graças às zero-cost abstractions — sem nenhum custo de performance em relação a loops manuais. Neste artigo, vamos explorar o trait Iterator em profundidade, dominar os combinadores mais importantes e criar iteradores customizados.

O Trait Iterator

No núcleo de tudo está o trait Iterator, surpreendentemente simples:

// Definição simplificada da biblioteca padrão
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;

    // + dezenas de métodos com implementação padrão
    // (map, filter, fold, collect, etc.)
}

Todo iterador precisa implementar apenas um método: next(). Todos os outros são derivados automaticamente.

Exemplo Básico

fn main() {
    let numeros = vec![1, 2, 3, 4, 5];
    let mut iter = numeros.iter(); // cria um iterador

    // next() retorna Option<&i32>
    println!("{:?}", iter.next()); // Some(1)
    println!("{:?}", iter.next()); // Some(2)
    println!("{:?}", iter.next()); // Some(3)
    println!("{:?}", iter.next()); // Some(4)
    println!("{:?}", iter.next()); // Some(5)
    println!("{:?}", iter.next()); // None — acabou
}

Três Formas de Criar Iteradores

fn main() {
    let dados = vec![String::from("a"), String::from("b"), String::from("c")];

    // .iter() → Iterator<Item = &T>
    // Não consome o vetor — empréstimo imutável
    for item in dados.iter() {
        println!("Referência: {}", item);
    }
    println!("Dados ainda acessíveis: {:?}", dados);

    // .iter_mut() → Iterator<Item = &mut T>
    // Não consome, mas permite mutação
    let mut dados_mut = dados.clone();
    for item in dados_mut.iter_mut() {
        item.push_str("!");
    }
    println!("Mutados: {:?}", dados_mut);

    // .into_iter() → Iterator<Item = T>
    // Consome o vetor — toma ownership
    for item in dados.into_iter() {
        println!("Owned: {}", item);
    }
    // println!("{:?}", dados); // ERRO: dados foi consumido
}

Lazy Evaluation: Iteradores São Preguiçosos

Uma característica fundamental: adaptadores de iteradores não fazem nada até serem consumidos:

fn main() {
    let numeros = vec![1, 2, 3, 4, 5];

    // Isso NÃO executa nada — apenas cria uma cadeia de adaptadores
    let _cadeia = numeros.iter()
        .map(|x| {
            println!("map: {}", x); // nunca é impresso!
            x * 2
        })
        .filter(|x| {
            println!("filter: {}", x); // nunca é impresso!
            *x > 4
        });

    println!("Nada aconteceu ainda...");

    // Agora sim — collect() consome o iterador
    let resultado: Vec<i32> = numeros.iter()
        .map(|x| {
            println!("map: {}", x);
            x * 2
        })
        .filter(|x| {
            println!("filter: {}", x);
            *x > 4
        })
        .collect();

    println!("Resultado: {:?}", resultado);
}

Isso é eficiente porque cada elemento é processado um por vez pela cadeia inteira, sem criar coleções intermediárias:

Processamento elemento a elemento:
─────────────────────────────────
1 → map(2) → filter(2 > 4? Não) → descartado
2 → map(4) → filter(4 > 4? Não) → descartado
3 → map(6) → filter(6 > 4? Sim) → resultado
4 → map(8) → filter(8 > 4? Sim) → resultado
5 → map(10) → filter(10 > 4? Sim) → resultado

Sem coleções intermediárias!

Combinadores Essenciais

map — Transformar cada elemento

fn main() {
    let nomes = vec!["ana", "bruno", "clara"];

    let maiusculos: Vec<String> = nomes.iter()
        .map(|nome| nome.to_uppercase())
        .collect();

    println!("{:?}", maiusculos); // ["ANA", "BRUNO", "CLARA"]
}

filter — Selecionar elementos

fn main() {
    let numeros: Vec<i32> = (1..=20).collect();

    let pares: Vec<&i32> = numeros.iter()
        .filter(|n| *n % 2 == 0)
        .collect();

    println!("Pares: {:?}", pares); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
}

filter_map — Filtrar e transformar ao mesmo tempo

fn main() {
    let textos = vec!["42", "abc", "7", "xyz", "100"];

    let numeros: Vec<i32> = textos.iter()
        .filter_map(|t| t.parse::<i32>().ok())
        .collect();

    println!("{:?}", numeros); // [42, 7, 100]
}

fold — Acumular em um resultado

fn main() {
    let numeros = vec![1, 2, 3, 4, 5];

    // Soma com fold
    let soma = numeros.iter().fold(0, |acc, x| acc + x);
    println!("Soma: {}", soma); // 15

    // Construir string com fold
    let texto = numeros.iter().fold(String::new(), |mut acc, x| {
        if !acc.is_empty() {
            acc.push_str(", ");
        }
        acc.push_str(&x.to_string());
        acc
    });
    println!("Texto: {}", texto); // 1, 2, 3, 4, 5

    // Encontrar máximo e mínimo simultaneamente
    let (min, max) = numeros.iter().fold((i32::MAX, i32::MIN), |(min, max), &x| {
        (min.min(x), max.max(x))
    });
    println!("Min: {}, Max: {}", min, max); // Min: 1, Max: 5
}

flat_map — Achatar iteradores aninhados

fn main() {
    let frases = vec!["olá mundo", "rust é incrível", "iteradores são poderosos"];

    let palavras: Vec<&str> = frases.iter()
        .flat_map(|frase| frase.split_whitespace())
        .collect();

    println!("{:?}", palavras);
    // ["olá", "mundo", "rust", "é", "incrível", "iteradores", "são", "poderosos"]
}

enumerate — Adicionar índice

fn main() {
    let frutas = vec!["maçã", "banana", "cereja"];

    for (i, fruta) in frutas.iter().enumerate() {
        println!("{}. {}", i + 1, fruta);
    }
}

zip — Combinar dois iteradores

fn main() {
    let nomes = vec!["Ana", "Bruno", "Clara"];
    let idades = vec![25, 30, 28];

    let pessoas: Vec<String> = nomes.iter()
        .zip(idades.iter())
        .map(|(nome, idade)| format!("{} ({} anos)", nome, idade))
        .collect();

    println!("{:?}", pessoas);
    // ["Ana (25 anos)", "Bruno (30 anos)", "Clara (28 anos)"]
}

chain — Concatenar iteradores

fn main() {
    let primeira_parte = vec![1, 2, 3];
    let segunda_parte = vec![4, 5, 6];

    let todos: Vec<i32> = primeira_parte.iter()
        .chain(segunda_parte.iter())
        .copied()
        .collect();

    println!("{:?}", todos); // [1, 2, 3, 4, 5, 6]
}

take, skip, take_while, skip_while

fn main() {
    let numeros: Vec<i32> = (1..=10).collect();

    let primeiros_3: Vec<&i32> = numeros.iter().take(3).collect();
    println!("Primeiros 3: {:?}", primeiros_3); // [1, 2, 3]

    let apos_3: Vec<&i32> = numeros.iter().skip(3).collect();
    println!("Após 3: {:?}", apos_3); // [4, 5, 6, 7, 8, 9, 10]

    let enquanto_menor_5: Vec<&i32> = numeros.iter()
        .take_while(|&&x| x < 5)
        .collect();
    println!("Enquanto < 5: {:?}", enquanto_menor_5); // [1, 2, 3, 4]

    let apos_menor_5: Vec<&i32> = numeros.iter()
        .skip_while(|&&x| x < 5)
        .collect();
    println!("Após < 5: {:?}", apos_menor_5); // [5, 6, 7, 8, 9, 10]
}

Consumidores: sum, product, min, max, count

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

    println!("Soma: {}", numeros.iter().sum::<i32>());        // 44
    println!("Produto: {}", numeros.iter().product::<i32>()); // 48600
    println!("Mínimo: {:?}", numeros.iter().min());           // Some(1)
    println!("Máximo: {:?}", numeros.iter().max());           // Some(9)
    println!("Contagem: {}", numeros.iter().count());         // 11

    // any e all
    println!("Algum > 8? {}", numeros.iter().any(|&x| x > 8));   // true
    println!("Todos > 0? {}", numeros.iter().all(|&x| x > 0));   // true
    println!("Todos > 5? {}", numeros.iter().all(|&x| x > 5));   // false

    // find e position
    println!("Primeiro > 5: {:?}", numeros.iter().find(|&&x| x > 5));      // Some(9)
    println!("Posição > 5: {:?}", numeros.iter().position(|&x| x > 5));    // Some(5)
}

collect — O Canivete Suíço

collect() é incrivelmente versátil — o tipo de retorno determina o que ele faz:

use std::collections::{HashMap, HashSet, BTreeSet};

fn main() {
    let dados = vec![("Ana", 25), ("Bruno", 30), ("Ana", 28)];

    // Coletar em Vec
    let nomes: Vec<&str> = dados.iter().map(|(nome, _)| *nome).collect();
    println!("Vec: {:?}", nomes);

    // Coletar em HashSet (remove duplicatas)
    let unicos: HashSet<&str> = dados.iter().map(|(nome, _)| *nome).collect();
    println!("HashSet: {:?}", unicos);

    // Coletar em BTreeSet (remove duplicatas + ordena)
    let ordenados: BTreeSet<&str> = dados.iter().map(|(nome, _)| *nome).collect();
    println!("BTreeSet: {:?}", ordenados);

    // Coletar em HashMap
    let mapa: HashMap<&str, i32> = dados.into_iter().collect();
    println!("HashMap: {:?}", mapa);

    // Coletar Result em Result<Vec, Error>
    let textos = vec!["1", "2", "abc", "4"];
    let resultado: Result<Vec<i32>, _> = textos.iter()
        .map(|t| t.parse::<i32>())
        .collect();
    println!("Result: {:?}", resultado); // Err(ParseIntError)

    // Coletar em String
    let chars = vec!['R', 'u', 's', 't'];
    let texto: String = chars.into_iter().collect();
    println!("String: {}", texto); // Rust
}

Criando Iteradores Customizados

Iterador Simples: Sequência de Fibonacci

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

impl Fibonacci {
    fn novo() -> 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_b = self.a + self.b;
        self.a = self.b;
        self.b = novo_b;
        Some(resultado) // iterador infinito
    }
}

fn main() {
    // Fibonacci é preguiçoso — podemos pegar apenas o que queremos
    let primeiros_10: Vec<u64> = Fibonacci::novo().take(10).collect();
    println!("Primeiros 10: {:?}", primeiros_10);
    // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

    // Primeiro Fibonacci maior que 1000
    let grande = Fibonacci::novo()
        .find(|&n| n > 1000)
        .unwrap();
    println!("Primeiro > 1000: {}", grande); // 1597

    // Soma dos Fibonacci menores que 100
    let soma: u64 = Fibonacci::novo()
        .take_while(|&n| n < 100)
        .sum();
    println!("Soma dos < 100: {}", soma); // 232
}

Iterador com Estado: Janela Deslizante

struct JanelaDeslizante<'a, T> {
    dados: &'a [T],
    tamanho: usize,
    posicao: usize,
}

impl<'a, T> JanelaDeslizante<'a, T> {
    fn nova(dados: &'a [T], tamanho: usize) -> Self {
        JanelaDeslizante {
            dados,
            tamanho,
            posicao: 0,
        }
    }
}

impl<'a, T> Iterator for JanelaDeslizante<'a, T> {
    type Item = &'a [T];

    fn next(&mut self) -> Option<Self::Item> {
        if self.posicao + self.tamanho <= self.dados.len() {
            let janela = &self.dados[self.posicao..self.posicao + self.tamanho];
            self.posicao += 1;
            Some(janela)
        } else {
            None
        }
    }

    // Otimização: implementar size_hint para collect() eficiente
    fn size_hint(&self) -> (usize, Option<usize>) {
        let restante = if self.dados.len() >= self.posicao + self.tamanho {
            self.dados.len() - self.posicao - self.tamanho + 1
        } else {
            0
        };
        (restante, Some(restante))
    }
}

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

    // Média móvel com janela de 3
    let medias: Vec<f64> = JanelaDeslizante::nova(&dados, 3)
        .map(|janela| {
            let soma: i32 = janela.iter().sum();
            soma as f64 / janela.len() as f64
        })
        .collect();

    println!("Médias móveis: {:?}", medias);
    // [2.0, 3.0, 4.0, 5.0, 6.0]

    // Nota: na prática, use .windows(3) que já existe nos slices
    let medias2: Vec<f64> = dados.windows(3)
        .map(|w| w.iter().sum::<i32>() as f64 / w.len() as f64)
        .collect();

    assert_eq!(medias, medias2);
}

Implementando IntoIterator para Tipos Próprios

#[derive(Debug)]
struct Matriz {
    dados: Vec<Vec<f64>>,
    linhas: usize,
    colunas: usize,
}

impl Matriz {
    fn nova(linhas: usize, colunas: usize) -> Self {
        Matriz {
            dados: vec![vec![0.0; colunas]; linhas],
            linhas,
            colunas,
        }
    }

    fn definir(&mut self, linha: usize, coluna: usize, valor: f64) {
        self.dados[linha][coluna] = valor;
    }
}

// Iterador sobre elementos da matriz (linha por linha)
struct MatrizIter<'a> {
    matriz: &'a Matriz,
    linha: usize,
    coluna: usize,
}

impl<'a> Iterator for MatrizIter<'a> {
    type Item = (usize, usize, f64);

    fn next(&mut self) -> Option<Self::Item> {
        if self.linha >= self.matriz.linhas {
            return None;
        }

        let resultado = (
            self.linha,
            self.coluna,
            self.matriz.dados[self.linha][self.coluna],
        );

        self.coluna += 1;
        if self.coluna >= self.matriz.colunas {
            self.coluna = 0;
            self.linha += 1;
        }

        Some(resultado)
    }
}

impl<'a> IntoIterator for &'a Matriz {
    type Item = (usize, usize, f64);
    type IntoIter = MatrizIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        MatrizIter {
            matriz: self,
            linha: 0,
            coluna: 0,
        }
    }
}

fn main() {
    let mut m = Matriz::nova(2, 3);
    m.definir(0, 0, 1.0);
    m.definir(0, 1, 2.0);
    m.definir(0, 2, 3.0);
    m.definir(1, 0, 4.0);
    m.definir(1, 1, 5.0);
    m.definir(1, 2, 6.0);

    // Agora podemos iterar com for
    for (l, c, v) in &m {
        println!("[{},{}] = {}", l, c, v);
    }

    // E usar combinadores
    let soma: f64 = m.into_iter().map(|(_, _, v)| v).sum();
    println!("Soma: {}", soma); // 21
}

Zero-Cost Abstractions: A Prova

Iteradores em Rust são compilados para código tão eficiente quanto loops manuais. Veja a equivalência:

fn soma_pares_imperativo(dados: &[i32]) -> i32 {
    let mut soma = 0;
    for i in 0..dados.len() {
        if dados[i] % 2 == 0 {
            soma += dados[i] * dados[i];
        }
    }
    soma
}

fn soma_pares_funcional(dados: &[i32]) -> i32 {
    dados.iter()
        .filter(|x| *x % 2 == 0)
        .map(|x| x * x)
        .sum()
}

fn main() {
    let dados: Vec<i32> = (1..=100).collect();

    let r1 = soma_pares_imperativo(&dados);
    let r2 = soma_pares_funcional(&dados);

    assert_eq!(r1, r2);
    println!("Ambos dão: {}", r1); // 171700
}

O compilador Rust gera assembly idêntico para ambas as versões (com otimizações ligadas). Isso é possível porque:

  1. Iteradores são structs com tamanho conhecido em tempo de compilação
  2. O método next() é inlined
  3. Não há alocação no heap
  4. Não há dispatch dinâmico

Erros Comuns

1. Esquecer de consumir o iterador

fn main() {
    let dados = vec![1, 2, 3];

    // AVISO: iterador não consumido — não faz nada!
    dados.iter().map(|x| println!("{}", x));

    // CORRETO: use for_each() para efeitos colaterais
    dados.iter().for_each(|x| println!("{}", x));

    // Ou use for
    for x in &dados {
        println!("{}", x);
    }
}

2. Tentar iterar duas vezes sobre um iterador consumido

fn main() {
    let dados = vec![1, 2, 3];
    let iter = dados.into_iter(); // consome dados

    let soma: i32 = iter.sum(); // consome iter
    // let produto: i32 = iter.product(); // ERRO: iter já foi consumido

    // SOLUÇÃO: crie iteradores separados
    let dados = vec![1, 2, 3];
    let soma: i32 = dados.iter().sum();
    let produto: i32 = dados.iter().product();
    println!("Soma: {}, Produto: {}", soma, produto);
}

Aplicação no Mundo Real: Processamento de Dados

use std::collections::HashMap;

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

fn main() {
    let vendas = vec![
        Venda { produto: "Notebook".into(), categoria: "Eletrônicos".into(), valor: 3500.0, quantidade: 2 },
        Venda { produto: "Mouse".into(), categoria: "Eletrônicos".into(), valor: 89.90, quantidade: 15 },
        Venda { produto: "Cadeira".into(), categoria: "Móveis".into(), valor: 899.0, quantidade: 3 },
        Venda { produto: "Teclado".into(), categoria: "Eletrônicos".into(), valor: 199.90, quantidade: 8 },
        Venda { produto: "Mesa".into(), categoria: "Móveis".into(), valor: 1200.0, quantidade: 2 },
        Venda { produto: "Monitor".into(), categoria: "Eletrônicos".into(), valor: 1800.0, quantidade: 4 },
    ];

    // Total por categoria
    let total_por_categoria: HashMap<&str, f64> = vendas.iter()
        .map(|v| (v.categoria.as_str(), v.valor * v.quantidade as f64))
        .fold(HashMap::new(), |mut acc, (cat, total)| {
            *acc.entry(cat).or_insert(0.0) += total;
            acc
        });

    println!("Total por categoria:");
    for (cat, total) in &total_por_categoria {
        println!("  {}: R${:.2}", cat, total);
    }

    // Produto com maior faturamento
    let top_produto = vendas.iter()
        .max_by(|a, b| {
            let fa = a.valor * a.quantidade as f64;
            let fb = b.valor * b.quantidade as f64;
            fa.partial_cmp(&fb).unwrap()
        })
        .unwrap();

    println!("\nTop produto: {} (R${:.2})",
        top_produto.produto,
        top_produto.valor * top_produto.quantidade as f64
    );

    // Produtos eletrônicos ordenados por valor
    let mut eletronicos: Vec<&Venda> = vendas.iter()
        .filter(|v| v.categoria == "Eletrônicos")
        .collect();
    eletronicos.sort_by(|a, b| b.valor.partial_cmp(&a.valor).unwrap());

    println!("\nEletrônicos (por valor):");
    for v in eletronicos {
        println!("  {} - R${:.2}", v.produto, v.valor);
    }
}

Veja Também