Iterators
Domine o trait Iterator, adaptadores (map, filter, fold, collect), adaptadores consumidores e avaliação lazy
Iterators
Iterators são a forma idiomática do Rust para processar sequências de valores. Eles são lazy, combináveis e compilam para código de máquina eficiente — frequentemente mais rápido que loops escritos à mão.
O Trait Iterator
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Muitos métodos padrão...
}Qualquer tipo que implemente Iterator pode ser usado com adaptadores de iterator:
struct Counter {
count: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let mut counter = Counter::new(3);
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), None);
}O trait Iterator só requer next(). Todos os outros métodos são implementações padrão construídas sobre next().
Adaptadores Consumidores
Estes chamam next() até None:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// collect — coleta em uma coleção
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
// sum — soma todos os valores
let sum: i32 = numbers.iter().sum();
println!("{sum}"); // 15
// count — conta elementos
let count = numbers.iter().count();
println!("{count}"); // 5
// fold — acumula com valor inicial
let product = numbers.iter().fold(1, |acc, x| acc * x);
println!("{product}"); // 120
// reduce — acumula sem valor inicial
let sum = numbers.iter().cloned().reduce(|a, b| a + b);
println!("{:?}", sum); // Some(15)
// for_each — efeitos colaterais
numbers.iter().for_each(|x| print!("{x} "));
println!();
// any / all — predicados
let has_even = numbers.iter().any(|x| x % 2 == 0);
let all_positive = numbers.iter().all(|x| x > &0);
println!("has_even: {has_even}, all_positive: {all_positive}");
}Adaptadores de Iterator (Lazy)
Adaptadores transformam um iterator em outro iterator. Eles são lazy — nada acontece até que um adaptador consumidor seja chamado:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// map — transforma cada elemento
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
// filter — mantém elementos que correspondem ao predicado
let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect();
println!("{:?}", evens); // [2, 4]
// filter_map — filtra e mapeia em uma passada
let parsed: Vec<i32> = vec!["1", "two", "3", "four"]
.iter()
.filter_map(|s| s.parse().ok())
.collect();
println!("{:?}", parsed); // [1, 3]
// flat_map — achata iterators aninhados
let words: Vec<String> = vec!["hello world", "rust is great"]
.iter()
.flat_map(|s| s.split_whitespace())
.map(String::from)
.collect();
println!("{:?}", words);
// take / skip
let first3: Vec<i32> = numbers.iter().take(3).cloned().collect();
let after2: Vec<i32> = numbers.iter().skip(2).cloned().collect();
println!("take: {first3:?}, skip: {after2:?}");
// chain — combina iterators
let combined: Vec<i32> = vec![1, 2].iter().chain(vec![3, 4].iter()).cloned().collect();
println!("{combined:?}"); // [1, 2, 3, 4]
// zip — pareia elementos de dois iterators
let names = vec!["Alice", "Bob", "Charlie"];
let scores = vec![90, 85, 95];
let paired: Vec<(&str, i32)> = names.iter().zip(scores.iter()).map(|(n, s)| (*n, *s)).collect();
println!("{paired:?}");
}Trait IntoIterator
Loops for usam IntoIterator para converter tipos em iterators:
fn main() {
let v = vec![1, 2, 3];
// IntoIterator::into_iter consome self
for x in v { // v é consumido
print!("{x} ");
}
// println!("{:?}", v); // ERROR: v movido
// &Vec implementa IntoIterator → produz &i32
let v = vec![1, 2, 3];
for x in &v {
print!("{x} "); // x: &i32
}
// &mut Vec implementa IntoIterator → produz &mut i32
let mut v = vec![1, 2, 3];
for x in &mut v {
*x *= 2;
}
}| IntoIterator em | Produz | Efeito |
|---|---|---|
Vec<T> | T | Consome o vector |
&Vec<T> | &T | Empresta |
&mut Vec<T> | &mut T | Empréstimo mutável |
Métodos de Iterator Personalizados
fn main() {
// Processamento em chunks
let data = vec![1, 2, 3, 4, 5, 6];
for chunk in data.chunks(2) {
println!("{:?}", chunk); // [1,2], [3,4], [5,6]
}
for window in data.windows(2) {
println!("{:?}", window); // [1,2], [2,3], [3,4], [4,5], [5,6]
}
}
// Iterator personalizado — Fibonacci
struct Fibonacci {
curr: u64,
next: u64,
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
self.curr = self.next;
self.next = current + self.next;
Some(current)
}
}
fn fibonacci() -> Fibonacci {
Fibonacci { curr: 0, next: 1 }
}
fn main() {
let fib: Vec<u64> = fibonacci().take(10).collect();
println!("{:?}", fib); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
}Performance — Iterators vs Loops
Os iterators do Rust compilam para o mesmo código de máquina que loops escritos à mão:
// Estes compilam para assembly IDÊNTICO:
fn sum_with_loop(v: &[i32]) -> i32 {
let mut sum = 0;
for i in 0..v.len() {
sum += v[i];
}
sum
}
fn sum_with_iter(v: &[i32]) -> i32 {
v.iter().sum()
}Iterators são abstrações de custo zero. O compilador os inline e otimiza, produzindo código equivalente à versão escrita à mão.
Exemplo Real: Pipeline de Processamento de Dados
use std::collections::HashMap;
#[derive(Debug)]
struct Sale {
product: String,
amount: f64,
quantity: u32,
}
fn analyze_sales(sales: Vec<Sale>) -> HashMap<String, f64> {
sales
.into_iter()
.map(|s| (s.product, s.amount * s.quantity as f64))
.fold(HashMap::new(), |mut acc, (product, total)| {
*acc.entry(product).or_insert(0.0) += total;
acc
})
}
fn process_log(lines: Vec<String>) -> Vec<(usize, String)> {
lines
.into_iter()
.enumerate()
.filter(|(_, line)| !line.trim().is_empty())
.filter(|(_, line)| !line.starts_with('#'))
.map(|(i, line)| (i + 1, line))
.collect()
}
fn main() {
let sales = vec![
Sale { product: "Widget".into(), amount: 10.0, quantity: 3 },
Sale { product: "Gadget".into(), amount: 25.0, quantity: 2 },
Sale { product: "Widget".into(), amount: 10.0, quantity: 1 },
Sale { product: "Gizmo".into(), amount: 15.0, quantity: 5 },
];
let revenue = analyze_sales(sales);
for (product, total) in &revenue {
println!("{product}: ${total:.2}");
}
let log = vec![
"# Comment".into(),
"INFO: started".into(),
"".into(),
"INFO: processing".into(),
"ERROR: failed".into(),
];
let cleaned = process_log(log);
println!("{:?}", cleaned);
}Perguntas de Prática
- Qual é o único método obrigatório no trait Iterator?
- Qual é a diferença entre adaptadores consumidores e lazy?
- Como
collectsabe em qual tipo coletar? - O que
filter_mapfaz quefilteremapseparados não podem? - Como
IntoIteratorpossibilita loopsfor? - Qual é a diferença entre
foldereduce? - Iterators são mais lentos que loops escritos à mão em Rust?
- Como criar um tipo de iterator personalizado?
- O que
flat_mapfaz? - Como processar itens em chunks usando iterators?