Adaptadores de iterador: map, filter, collect, fold
1. Introdução aos Adaptadores de Iterador
Em Rust, os adaptadores de iterador são métodos que transformam ou filtram sequências de dados sem executar a computação imediatamente. Eles seguem o princípio de lazy evaluation: você constrói um pipeline de transformações, mas nada é processado até que um consumidor (consumer) seja chamado.
A diferença fundamental é:
- Adaptadores (lazy): map, filter, fold — retornam um novo iterador que ainda não foi avaliado.
- Consumidores (eager): collect, sum, for_each — disparam a execução do pipeline.
A mágica está no encadeamento de métodos. Você pode construir cadeias complexas como:
let resultado: Vec<i32> = (1..10)
.filter(|x| x % 2 == 0)
.map(|x| x * 3)
.collect();
Isso é mais expressivo e seguro que loops manuais, pois o compilador verifica tipos em cada etapa.
2. map: Transformando Elementos
O adaptador map aplica uma closure a cada elemento do iterador, produzindo um novo iterador com os resultados. Sua assinatura é:
fn map<B, F>(self, f: F) -> Map<Self, F>
where
F: FnMut(Self::Item) -> B,
A closure pode capturar contexto externo de três formas:
let fator = 3;
let numeros = vec![1, 2, 3];
// Captura por referência (imutável)
let iter1 = numeros.iter().map(|x| x * fator);
// Captura mutável (cuidado com empréstimos)
let mut acumulador = 0;
let iter2 = numeros.iter().map(|x| {
acumulador += x;
x * 2
});
// Captura por movimento (`move`)
let nome = String::from("item");
let iter3 = numeros.iter().map(move |x| format!("{}-{}", nome, x));
Exemplo prático: converter strings para números:
let textos = vec!["10", "20", "30", "40"];
let numeros: Vec<i32> = textos
.iter()
.map(|s| s.parse::<i32>().unwrap())
.collect();
println!("{:?}", numeros); // [10, 20, 30, 40]
3. filter: Selecionando Elementos
filter mantém apenas os elementos que satisfazem uma condição booleana:
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
P: FnMut(&Self::Item) -> bool,
Note que a closure recebe uma referência ao elemento, não o elemento em si. Exemplo básico:
let numeros = vec![1, 2, 3, 4, 5, 6];
let pares: Vec<i32> = numeros
.iter()
.filter(|&&x| x % 2 == 0)
.copied()
.collect();
println!("{:?}", pares); // [2, 4, 6]
Combinando filter com map:
let dados = vec![1, 2, 3, 4, 5, 6];
let resultado: Vec<i32> = dados
.iter()
.filter(|&&x| x > 2) // mantém 3, 4, 5, 6
.map(|&x| x * 10) // transforma em 30, 40, 50, 60
.collect();
println!("{:?}", resultado);
Cuidado com capturas mutáveis: closures em filter podem capturar variáveis mutáveis, mas isso pode causar problemas com empréstimos simultâneos:
let mut limite = 3;
let numeros = vec![1, 2, 3, 4, 5];
// Isso compila, mas é frágil:
let filtrados: Vec<i32> = numeros
.iter()
.filter(|&&x| {
let resultado = x > limite;
if resultado {
limite += 1; // mutação aqui
}
resultado
})
.copied()
.collect();
println!("{:?}", filtrados);
Em geral, prefira closures que não modifiquem estado externo.
4. collect: Coletando Resultados
collect é o consumidor mais versátil: transforma um iterador em qualquer coleção que implemente FromIterator. Sua assinatura:
fn collect<B: FromIterator<Self::Item>>(self) -> B
O tipo de retorno deve ser inferido explicitamente ou anotado:
let v: Vec<i32> = (1..5).collect(); // Vec
let s: HashSet<i32> = (1..5).collect(); // HashSet
let m: String = vec!["a", "b"].into_iter().collect(); // String
Coletando em Result e Option:
let resultados = vec![Ok(1), Ok(2), Ok(3)];
let todos_ok: Result<Vec<i32>, ()> = resultados.into_iter().collect();
assert_eq!(todos_ok, Ok(vec![1, 2, 3]));
let com_erro = vec![Ok(1), Err("falha"), Ok(3)];
let parcial: Result<Vec<i32>, &str> = com_erro.into_iter().collect();
assert_eq!(parcial, Err("falha")); // para no primeiro erro
Para Option:
let alguns: Vec<Option<i32>> = vec![Some(1), Some(2), Some(3)];
let todos: Option<Vec<i32>> = alguns.into_iter().collect();
assert_eq!(todos, Some(vec![1, 2, 3]));
let com_none = vec![Some(1), None, Some(3)];
let resultado: Option<Vec<i32>> = com_none.into_iter().collect();
assert_eq!(resultado, None);
5. fold: Redução e Acumulação
fold é o adaptador que reduz um iterador a um único valor, usando um acumulador. Sua assinatura:
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
Exemplos clássicos:
let numeros = vec![1, 2, 3, 4, 5];
// Soma
let soma = numeros.iter().fold(0, |acc, x| acc + x);
assert_eq!(soma, 15);
// Produto
let produto = numeros.iter().fold(1, |acc, x| acc * x);
assert_eq!(produto, 120);
// Concatenação de strings
let palavras = vec!["Rust", "é", "incrível"];
let frase = palavras.iter().fold(String::new(), |acc, s| {
if acc.is_empty() { s.to_string() } else { format!("{} {}", acc, s) }
});
assert_eq!(frase, "Rust é incrível");
Comparação com reduce:
// reduce retorna Option (None para iterador vazio)
let vazio: Vec<i32> = vec![];
let resultado_reduce = vazio.iter().reduce(|acc, x| acc + x);
assert_eq!(resultado_reduce, None);
// fold sempre retorna o valor inicial
let resultado_fold = vazio.iter().fold(0, |acc, x| acc + x);
assert_eq!(resultado_fold, 0);
6. Combinações e Encadeamentos Avançados
Pipelines complexos são naturais em Rust:
let dados = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
let resultado = dados
.iter()
.enumerate() // adiciona índice
.filter(|&(i, &v)| i % 2 == 0) // posições pares
.map(|(_, &v)| v * 2) // dobra valores
.fold(0, |acc, v| acc + v); // soma
println!("Soma dos valores dobrados em posições pares: {}", resultado);
Usando zip com adaptadores:
let nomes = vec!["Alice", "Bob", "Carol"];
let idades = vec![30, 25, 35];
let pessoas: Vec<String> = nomes
.iter()
.zip(idades.iter())
.map(|(nome, idade)| format!("{} tem {} anos", nome, idade))
.collect();
println!("{:?}", pessoas);
Lazy evaluation: O pipeline só executa quando collect ou outro consumidor é chamado:
let iter = (1..10)
.filter(|x| {
println!("Filtrando {}", x);
x % 2 == 0
})
.map(|x| {
println!("Mapeando {}", x);
x * 2
});
// Nada foi impresso ainda!
println!("Pipeline construído");
let resultado: Vec<i32> = iter.collect(); // Agora executa
println!("{:?}", resultado);
Saída:
Pipeline construído
Filtrando 1
Filtrando 2
Mapeando 2
Filtrando 3
Filtrando 4
Mapeando 4
...
7. Tratamento de Erros com Adaptadores
filter_map combina filtragem e transformação em um único passo, usando Option:
let entradas = vec!["1", "2a", "3", "quatro", "5"];
let numeros_validos: Vec<i32> = entradas
.iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("{:?}", numeros_validos); // [1, 3, 5]
Trabalhando com Result e propagação de erros:
use std::num::ParseIntError;
fn processar_linhas(linhas: &[&str]) -> Result<Vec<i32>, ParseIntError> {
linhas
.iter()
.map(|s| s.parse::<i32>())
.collect() // coleta Result<Vec<i32>, ParseIntError>
}
let dados_ok = vec!["10", "20", "30"];
let dados_erro = vec!["10", "vinte", "30"];
println!("{:?}", processar_linhas(&dados_ok)); // Ok([10, 20, 30])
println!("{:?}", processar_linhas(&dados_erro)); // Err(ParseIntError)
Exemplo prático com fallback:
let linhas = vec!["42", "invalido", "100", "erro", "7"];
let processados: Vec<i32> = linhas
.iter()
.map(|s| s.parse::<i32>().unwrap_or(0)) // fallback para 0
.collect();
println!("{:?}", processados); // [42, 0, 100, 0, 7]
8. Considerações Finais e Boas Práticas
Quando usar adaptadores vs loops tradicionais:
- Use adaptadores para pipelines claros e transformações em sequência.
- Use loops quando a lógica for muito complexa ou precisar de
break/continuenão triviais. - O compilador otimiza ambos igualmente bem; escolha pela legibilidade.
Evite closures muito complexas:
// Ruim: closure gigante dentro do map
let resultado: Vec<i32> = dados.iter().map(|x| {
// 20 linhas de lógica aqui
x * 2 + 3
}).collect();
// Bom: extraia para função nomeada
fn transformar(x: &i32) -> i32 {
// 20 linhas de lógica aqui
x * 2 + 3
}
let resultado: Vec<i32> = dados.iter().map(transformar).collect();
Use clippy para sugestões idiomáticas:
cargo clippy
Clippy sugere substituir filter().map() por filter_map(), usar fold em vez de loops manuais, e muito mais.
Os adaptadores de iterador são uma das features mais elegantes de Rust. Eles combinam segurança de tipos, expressividade funcional e performance zero-cost. Domine-os e seu código ficará mais limpo, seguro e idiomático.
Referências
- Documentação oficial: Iterator trait — Referência completa de todos os métodos disponíveis no trait Iterator, incluindo map, filter, collect e fold.
- Rust by Example: Iterators — Tutoriais práticos com exemplos de uso de adaptadores de iterador em Rust.
- The Rust Programming Language (Book) - Chapter 13: Iterators and Closures — Capítulo oficial do livro sobre iteradores, closures e lazy evaluation.
- Rust Iterator Cheat Sheet — Referência visual rápida com exemplos de todos os adaptadores e consumidores de iterador.
- Effective Rust: Iterators — Guia de boas práticas para usar iteradores de forma idiomática e eficiente em Rust.
Referências (continuação)
- Rust Clippy Documentation — Documentação oficial do linter Clippy, com sugestões para escrever código Rust mais idiomático, incluindo recomendações sobre uso de adaptadores de iterador.
- Rust Performance Book: Iterators — Guia de performance sobre iteradores em Rust, explicando como o compilador otimiza pipelines de adaptadores.
Conclusão
Os adaptadores de iterador em Rust — map, filter, collect, fold e seus companheiros — formam a espinha dorsal da programação funcional na linguagem. Eles permitem transformar, filtrar, acumular e coletar dados de forma declarativa, segura e eficiente, sem sacrificar performance.
A lazy evaluation garante que apenas o necessário seja computado, enquanto a inferência de tipos e o sistema de ownership tornam o código robusto contra erros comuns. Combinados com closures, esses adaptadores criam pipelines expressivos que substituem loops verbosos por cadeias de métodos legíveis e modulares.
Lembre-se das boas práticas:
- Prefira filter_map a filter().map() quando possível
- Extraia closures complexas para funções nomeadas
- Use clippy para manter seu código idiomático
- Escolha entre adaptadores e loops baseado na clareza, não na performance (o compilador otimiza ambos)
Com esses conceitos dominados, você está preparado para escrever código Rust mais elegante, funcional e eficiente. Aproveite o poder dos iteradores!