Entendendo Iteradores em Rust
Iteradores são um conceito fundamental em Rust que representam uma sequência de elementos que podem ser processados um de cada vez. Diferente de linguagens como Python ou JavaScript, Rust oferece iteradores com segurança de memória e performance excepcional. Um iterador implementa a trait Iterator, que exige apenas um método: next(). Este método retorna Option<T>, sendo Some(valor) enquanto há elementos e None quando termina. A beleza dos iteradores em Rust está na combinação com os adaptadores (como map e filter), que permitem transformações expressivas e eficientes.
fn main() {
let numeros = vec![1, 2, 3, 4, 5];
let iter = numeros.iter();
// Iteradores são lazy, não fazem nada até serem consumidos
for num in iter {
println!("{}", num);
}
}
Map, Filter e Fold: Os Pilares Funcionais
Map: Transformando Elementos
O map é um adaptador que transforma cada elemento do iterador usando uma função. Ele retorna um novo iterador com os elementos transformados, sem consumir o original. Este é um operador funcional puro que não modifica o estado original dos dados. O map é particularmente poderoso quando combinado com outras operações, criando pipelines de transformação elegantes e legíveis.
fn main() {
let numeros = vec![1, 2, 3, 4, 5];
// Map transforma cada elemento
let dobrados: Vec<i32> = numeros
.iter()
.map(|x| x * 2)
.collect();
println!("{:?}", dobrados); // [2, 4, 6, 8, 10]
}
Filter: Selecionando Elementos
O filter retorna um iterador contendo apenas elementos que satisfazem uma condição (predicado). Como o map, ele também é lazy e não consome memória desnecessária. A combinação de filter com map é extremamente comum em Rust para operações de processamento de dados. O filter recebe uma closure que retorna um booleano, e mantém apenas elementos onde a condição é true.
fn main() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Filter seleciona pares
let pares: Vec<i32> = numeros
.iter()
.filter(|x| *x % 2 == 0)
.map(|x| x * 10)
.collect();
println!("{:?}", pares); // [20, 40, 60, 80, 100]
}
Fold: Acumulando Resultados
O fold (também conhecido como reduce) é um consumidor que reduz um iterador a um único valor, acumulando resultados através de uma função. Diferente de map e filter, o fold consome o iterador e exige um valor inicial (acumulador). É a ferramenta ideal para cálculos como somas, produtos, concatenações ou qualquer agregação de dados. O fold recebe dois parâmetros: o acumulador inicial e uma closure que recebe o acumulador e o elemento atual.
fn main() {
let numeros = vec![1, 2, 3, 4, 5];
// Fold calcula a soma
let soma = numeros
.iter()
.fold(0, |acum, x| acum + x);
println!("{}", soma); // 15
// Fold com strings
let palavras = vec!["Rust", "é", "incrível"];
let frase = palavras
.iter()
.fold(String::new(), |mut acum, palavra| {
if !acum.is_empty() {
acum.push(' ');
}
acum.push_str(palavra);
acum
});
println!("{}", frase); // "Rust é incrível"
}
Lazy Evaluation: O Segredo da Performance
Como Funciona a Avaliação Preguiçosa
A lazy evaluation é o conceito chave que torna os iteradores em Rust tão eficientes. Quando você chama map ou filter, nenhuma operação acontece imediatamente. Estes adaptadores retornam novos iteradores que especificam como transformar os dados, mas não executam a transformação até que o iterador seja consumido por um método terminal como collect(), fold(), ou um loop for. Isso significa que você pode criar pipelines complexos com múltiplas transformações e Rust otimizará tudo junto, evitando alocações de memória intermediárias.
fn main() {
let numeros = vec![1, 2, 3, 4, 5];
// Nada acontece aqui ainda!
let _resultado = numeros
.iter()
.filter(|x| *x > 2)
.map(|x| x * 2);
// Só aqui o iterador é consumido e processado
let vetor_final: Vec<i32> = numeros
.iter()
.filter(|x| *x > 2)
.map(|x| x * 2)
.collect();
println!("{:?}", vetor_final); // [6, 8, 10]
}
Exemplo Prático: Pipeline Complexo
A combinação de lazy evaluation com múltiplos adaptadores cria código expressivo e performático. No exemplo abaixo, Rust analisa todo o pipeline e o otimiza em uma única passagem pelos dados, sem criar vetores intermediários. Este é um padrão extremamente comum em processamento de dados em Rust e demonstra por que a linguagem é tão eficiente.
fn main() {
let dados = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let resultado: i32 = dados
.iter()
.filter(|x| *x % 2 == 0) // Filtra pares
.map(|x| x * x) // Eleva ao quadrado
.fold(0, |acum, x| acum + x); // Soma tudo
// Uma única passagem, sem alocações intermediárias
println!("Resultado: {}", resultado); // 4 + 16 + 36 + 100 = 156
}
Conclusão
Dominar iteradores em Rust é fundamental para escrever código idiomático e eficiente. Os três pontos principais aprendidos foram: (1) map e filter são adaptadores lazy que transformam e selecionam elementos sem consumir memória extra, enquanto fold é um consumidor que reduz dados a um único valor; (2) lazy evaluation permite que Rust otimize pipelines complexos em uma única passagem, eliminando alocações intermediárias e tornando o código performático por padrão; (3) a combinação destes operadores cria código funcional, expressivo e seguro, que é testável e fácil de manter. Pratique combinando estes adaptadores em seus projetos e você verá como eles se tornam naturais e poderosos.