Rust: Iteradores em Rust
Última atualização: 2026-08-26
Um iterador é o “pipeline de processamento de dados preguiçoso” do Rust — ele não calcula os resultados imediatamente, mas produz os elementos um por um, permitindo que você processe sequências de dados de forma declarativa por meio de chamadas encadeadas.
Um iterador é como uma linha de montagem de fábrica: os dados entram por uma extremidade, passam por uma série de processos (filtragem, transformação, extração, agregação) e, por fim, são produzidos como um produto acabado na outra extremidade. Cada processo realiza apenas uma tarefa, mas, quando combinados, eles podem realizar tarefas de processamento complexas.
1. A história de uma fábrica com linha de montagem
(1) Dificuldade: Processar dados com loops é tedioso e demorado
Xiao Ming é o supervisor da linha de montagem da fábrica da Rust. Ele precisa processar um lote de dados de peças:
- Separe todos os produtos qualificados (itens com números pares)
- Marque cada parte duas vezes
- Considere apenas os primeiros 5
- Totais estatísticos
Ele escreveu isso usando um loop tradicional for:
fn main() {
let parts = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut result = vec![];
let mut count = 0;
for &part in &parts {
if part % 2 == 0 { // Steps1: Filter
let doubled = part * 2; // Steps2: Convert
result.push(doubled);
count += 1;
if count == 5 { // Steps3: Excerpt
break;
}
}
}
let sum: i32 = result.iter().sum();
println!("Result: {:?}, Sum: {}", result, sum);
}
Embora o código funcione, a lógica está espalhada por todo o programa. Se o requisito mudar para “pular os dois primeiros novamente” ou “selecionar um número par de itens novamente”, seria necessário reescrever todo o loop.
(2) A abordagem do pipeline de iteradores
Reescreva a lógica acima usando chamadas encadeadas de iteradores:
fn main() {
let parts = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum: i32 = parts.iter()
.filter(|&&n| n % 2 == 0) // Steps1: Filter for Even Numbers
.map(|&n| n * 2) // Steps2: multiply by2
.take(5) // Steps3: Take the first 5
.sum(); // Steps4: Sum
println!("Sum: {}", sum);
}
O código passou de “como” para “o quê”. Cada linha representa um processo independente que pode ser inserido, excluído ou reordenado a qualquer momento — assim como o reajuste das estações de trabalho em uma linha de montagem.
2. Diagramas conceituais
O diagrama Mermaid a seguir ilustra o processo completo de avaliação preguiçosa da cadeia de métodos da característica Iterator, de iter() a collect():
graph LR
A["Raw Data<br/>1..=20"] --> B["iter()<br/>Create an iterator"]
B --> C["filter(|n| n%2==0)<br/>Filter for Even Numbers"]
C --> D["map(|n| n*3)<br/>Each element ×3"]
D --> E["skip(2)<br/>Skip the first 2"]
E --> F["take(5)<br/>Take the first 5"]
F --> G["collect()<br/>Consumer: Triggered Evaluation"]
H["Lazy Evaluation: Build the pipeline only<br/>Do not calculate immediately"] -.-> C
H -.-> D
H -.-> E
H -.-> F
I["Consumer-Driven Execution<br/>Produce the final result"] -.-> G
G --> J["Results<br/>[18, 24, 30, 36, 42]"]
3. O que você vai aprender
- A característica Iterator e o método
next: O contrato central de um iterador; compreender comonext()produz elementos um por um - Adaptadores de iterador:
map/filter/take/skip/chaine outros métodos de conversão - Consumidor:
collect/sum/count/fold, etc. — métodos que fazem com que o iterador seja executado - Avaliação preguiçosa: O adaptador não é executado imediatamente; ele aguarda até que o consumidor seja chamado.
- Iteradores personalizados: Implemente
Iterator traitpara seus próprios tipos - Guia prático para chamadas em cadeia: Combinando adaptadores e consumidores para realizar tarefas de processamento de dados
4. Conceitos fundamentais
graph TB
A[Iterator Iterator] --> B[Iterator Adapters<br>Adapter]
A --> C[Consumer Consumer]
B --> D["map(|x| x+1) Convert"]
B --> E["filter(|x| x>0) Filter"]
B --> F["take(n) Take the first n"]
B --> G["skip(n) Skip n"]
B --> H["chain(other) Concat"]
B --> I["enumerate() Add an index"]
C --> J["collect() Collected into a set"]
C --> K["sum() Sum"]
C --> L["count() Count"]
C --> M["fold(init, fn) Collapse"]
C --> N["for x in iter Loop"]
B -.->|"❌ Inertia<br>If it isn't called, it won't run."| C
C --> O["Triggered Evaluation"]
(1) Adaptadores de iterador x Consumidores
| Recurso | Adaptador de iterador | Consumidor |
|---|---|---|
| Função | Converter um iterador (de um iterador para outro) | Executar o iterador e gerar o resultado final |
| Valor de retorno | Novo tipo para Iterator |
Valores específicos (por exemplo, Vec<T>, i32, usize) |
| Preguiçoso | Preguiçoso (não executado imediatamente) | Avarento (executado imediatamente) |
| Método típico | map, filter, take, skip, chain |
collect, sum, count, fold, for_each |
| Posição na cadeia | Etapa intermediária | Etapa final |
| Exemplo | `.map( | x |
(2) Referência rápida para adaptadores de iterador comuns
| Adaptador | Função | Exemplo | Resultado |
|---|---|---|---|
map(f) |
Aplicar a transformação f a cada elemento | [1,2,3].iter().map(|x| x*2) |
2, 4, 6 |
filter(p) |
Manter os elementos que satisfazem a condição p | [1..=5].filter(|x| x%2==0) |
2, 4 |
take(n) |
Selecione apenas os primeiros n elementos | [1..].take(3) |
1, 2, 3 |
skip(n) |
Ignorar os primeiros n elementos | [1..=5].skip(2) |
3, 4, 5 |
chain(it) |
Adicionar outro iterador | [1,2].iter().chain([3,4].iter()) |
1, 2, 3, 4 |
enumerate() |
Atribuir um índice a cada elemento (i, val) |
['a','b'].iter().enumerate() |
(0,'a'), (1,'b') |
zip(it) |
Emparelhe os dois iteradores, um por um | [1,2].iter().zip(['a','b'].iter()) |
(1,'a'), (2,'b') |
rev() |
Iterador reverso | [1..=3].rev() |
3, 2, 1 |
(3) Referência rápida aos métodos comuns de consumo
| Consumidor | Efeito | Exemplo | Tipo de retorno | É ganancioso |
|---|---|---|---|---|
collect() |
Colecionar como conjunto | iter.collect::<Vec<_>>() |
B: FromIterator |
Sim |
sum() |
Soma | iter.sum::<i32>() |
S: Sum |
Sim |
count() |
contagem | iter.count() |
usize |
Sim |
fold(init, f) |
Cálculo cumulativo | iter.fold(0, |acc, x| acc + x) |
Tipo de valor inicial | Sim |
reduce(f) |
Colapso sem valor inicial | iter.reduce(|a, b| a + b) |
Option<Item> |
Sim |
for_each(f) |
Executar um por um (sem valor de retorno) | iter.for_each(|x| println!(x)) |
() |
Sim |
any(p) |
Existe alguma correspondência? | iter.any(|x| x > 0) |
bool |
Sim |
all(p) |
Todas as condições foram atendidas? | iter.all(|x| x > 0) |
bool |
Sim |
find(p) |
Encontre o primeiro que atenda aos critérios | iter.find(|x| *x > 3) |
Option<Item> |
Sim |
max() / min() |
Máx./Mín. | iter.max() |
Option<Item> |
Sim |
5. Exemplos
▶ Exemplo 1: A característica Iterator e o método next() — Compreendendo a essência dos iteradores (Dificuldade ⭐)
// ============================================
// Manual Invocation next() Understanding How Iterators Work
// ============================================
fn main() {
let numbers = vec![10, 20, 30, 40, 50];
// iter() Returns an iterator,Does not consume vectors
let mut iter = numbers.iter();
// next() Every time it returns Option<&T>
// Some(&value) Indicates that there is another element
// None Indicates the end of the iteration
println!("{:?}", iter.next()); // Some(10)
println!("{:?}", iter.next()); // Some(20)
println!("{:?}", iter.next()); // Some(30)
println!("{:?}", iter.next()); // Some(40)
println!("{:?}", iter.next()); // Some(50)
println!("{:?}", iter.next()); // None
println!("{:?}", iter.next()); // None (A subsequent call still returns None)
// for A loop is next() syntactic sugar
let mut count = 0;
let iter2 = numbers.iter();
for val in iter2 {
println!("for loop #{}: {}", count, val);
count += 1;
}
}
Resultado:
Some(10)
Some(20)
Some(30)
Some(40)
Some(50)
None
None
for loop #0: 10
for loop #1: 20
for loop #2: 30
for loop #3: 40
for loop #4: 50
O princípio fundamental de um iterador é o método
next(): cada chamada retornaSome(element)e, quando esgotado, retornaNone. O loopforé um recurso sintático que chama repetidamentenext()até encontrarNone. Compreender isso significa compreender os fundamentos de todos os iteradores.
▶ Exemplo 2: Encadeamento de adaptadores de iterador — map / filter / take / skip (Dificuldade: ⭐⭐)
// ============================================
// Combine Multiple Adapters to Build a Data Processing Pipeline
// ============================================
fn main() {
// Raw Data: 1 to 20
let data = 1..=20;
// Pipeline: Filter for Even Numbers → multiply by 3 → Skip the first 2 → Take the first 5
let result: Vec<i32> = data
.filter(|&n| n % 2 == 0) // [2,4,6,8,10,12,14,16,18,20]
.map(|n| n * 3) // [6,12,18,24,30,36,42,48,54,60]
.skip(2) // [18,24,30,36,42]
.take(5) // [18,24,30,36,42]
.collect(); // Triggered Evaluation,Collected Vec
println!("Pipeline result: {:?}", result);
// Another pipeline: use chain to Concat two slices
let first = vec!["A", "B", "C"];
let second = vec!["X", "Y", "Z"];
let combined: Vec<&str> = first.iter()
.chain(second.iter())
.copied()
.collect();
println!("Chained: {:?}", combined);
// Use enumerate to index elements
let fruits = vec!["apple", "banana", "cherry"];
let indexed: Vec<(usize, &str)> = fruits.iter()
.enumerate()
.map(|(i, &name)| (i + 1, name))
.collect();
println!("Indexed: {:?}", indexed);
}
Resultado:
Pipeline result: [18, 24, 30, 36, 42]
Chained: ["A", "B", "C", "X", "Y", "Z"]
Indexed: [(1, "apple"), (2, "banana"), (3, "cherry")]
Uma cadeia de adaptadores é como a disposição das estações de trabalho em uma linha de montagem: cada adaptador realiza apenas uma tarefa, e os dados fluem sequencialmente por cada estação de trabalho.
collect()é o sinal de demanda no final do pipeline — sem ele, os trabalhadores no pipeline não começam a trabalhar (avaliação preguiçosa).
▶ Exemplo 3: Iteradores personalizados — Implementando a característica Iterator para o seu tipo (Dificuldade: ⭐⭐)
// ============================================
// Custom Fibonacci Iterator
// Implementation Iterator trait Make any type iterable
// ============================================
// Fibonacci Sequence Generator
struct Fibonacci {
current: u64,
next: u64,
max: u64,
}
impl Fibonacci {
fn new(max: u64) -> Self {
Fibonacci {
current: 0,
next: 1,
max,
}
}
}
// Implementation Iterator trait It is the core of custom iterators.
impl Iterator for Fibonacci {
// Item The type of the elements returned by the iterator
type Item = u64;
// next() Back Option<Self::Item>
// Some(value) Indicates that there is another element
// None Indicates the end of the iteration
fn next(&mut self) -> Option<Self::Item> {
if self.current > self.max {
return None;
}
let result = self.current;
// Update to the next Fibonacci number
let new_next = self.current + self.next;
self.current = self.next;
self.next = new_next;
Some(result)
}
}
fn main() {
println!("Fibonacci up to 50:");
let fib = Fibonacci::new(50);
// Fibonacci It can now be used in for In a loop
for (i, n) in fib.enumerate() {
println!(" fib({}) = {}", i, n);
}
// It can also be used in conjunction with an adapter chain
println!("\nEven Fibonacci numbers up to 100:");
let even_fibs: Vec<u64> = Fibonacci::new(100)
.filter(|&n| n % 2 == 0)
.collect();
println!("{:?}", even_fibs);
}
Resultado:
Fibonacci up to 50:
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
Even Fibonacci numbers up to 100:
[0, 2, 8, 34]
Para implementar
Iterator trait, basta fazer uma coisa: definirtype Item(tipo de elemento) efn next()(regra de produção). Uma vez implementado, seu tipo ganha automaticamente todos os métodos adaptadores (map,filter,take, etc.) — essa é a concretização do conceito de “duck typing” no Rust: se você implementarnext(), ele poderá ser usado exatamente como um iterador.
▶ Exemplo 4: O consumidor em ação — dobrar / somar / contar / coletar (Dificuldade ⭐⭐⭐)
// ============================================
// Consumer:Drive the iterator to execute and produce the final result
// ============================================
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// sum() — Sum
let total: i32 = numbers.iter().sum();
println!("Sum: {}", total);
// count() — Count
let cnt = numbers.iter().count();
println!("Count: {}", cnt);
// fold() — General Folding Operation (initial value + accumulator closure)
// Here, we calculate 10!
let factorial: u64 = (1..=10u64).fold(1, |acc, x| acc * x);
println!("10! = {}", factorial);
// fold() — Manual Implementation of sum and count
let sum_via_fold: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
let count_via_fold: usize = numbers.iter().fold(0, |acc, _| acc + 1);
println!("Sum (via fold): {}", sum_via_fold);
println!("Count (via fold): {}", count_via_fold);
// collect() — Collected various types of collections
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
println!("Doubled: {:?}", doubled);
let even_set: std::collections::HashSet<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.copied()
.collect();
println!("Even set: {:?}", even_set);
// General: find the sum of the first 5 even squares
let complex_result: i32 = (1..=100)
.filter(|&n| n % 2 == 0)
.map(|n| n * n)
.take(5)
.fold(0, |acc, n| acc + n);
println!("Sum of first 5 even squares: {}", complex_result);
}
Resultado:
Sum: 55
Count: 10
10! = 3628800
Sum (via fold): 55
Count (via fold): 10
Doubled: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Even set: {2, 4, 6, 8, 10}
Sum of first 5 even squares: 220
Os consumidores são os componentes-chave no final do pipeline:
sum(),count()efold()calculam diretamente os resultados numéricos;collect()coleta dados em um conjunto.fold()é o consumidor de uso mais geral —sum()ecount()são, essencialmente, formas especializadas defold(). Um pipeline precisa ter um consumidor para ser efetivamente executado; caso contrário, tudo não passa de conversa.
▶ Exemplo 5: Exercício abrangente — Implementação de um iterador personalizado para criar um gerador de FizzBuzz (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example:Custom Iterators + Adapter Chain
// ============================================
struct FizzBuzz {
current: u32,
limit: u32,
}
impl FizzBuzz {
fn new(limit: u32) -> Self {
FizzBuzz { current: 0, limit }
}
}
impl Iterator for FizzBuzz {
type Item = String;
fn next(&mut self) -> Option<String> {
self.current += 1;
if self.current > self.limit {
return None;
}
let n = self.current;
let result = match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
_ => n.to_string(),
};
Some(result)
}
}
struct Fibonacci {
curr: u64,
next: u64,
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let result = self.curr;
self.curr = self.next;
self.next = result + self.next;
Some(result)
}
}
fn main() {
println!("=== FizzBuzz (1-20) ===");
for item in FizzBuzz::new(20) {
print!("{} ", item);
}
println!();
let fizz_count = FizzBuzz::new(100)
.filter(|s| s.starts_with("Fizz"))
.count();
println!("1-100 Fizz occurrences: {}", fizz_count);
println!("\n=== Fibonacci first 15 terms ===");
let fib = Fibonacci { curr: 0, next: 1 };
for val in fib.take(15) {
print!("{} ", val);
}
println!();
let fib_sum: u64 = Fibonacci { curr: 1, next: 1 }
.take_while(|&x| x < 1_000_000)
.filter(|&x| x % 2 == 0)
.sum();
println!("Fibonacci < 1M sum of even numbers: {}", fib_sum);
}
Resultado:
=== FizzBuzz (1-20) ===
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
1-100 Fizz occurrences: 27
=== Fibonacci first 15 terms ===
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
Fibonacci < 1M sum of even numbers: 1089154
Para criar um iterador personalizado, basta implementar o método
next()da característicaIterator.FizzBuzzOs geradores podem ser encadeados usandofilter/count;Fibonaccios iteradores infinitos podem limitar sua saída usandotake/take_whilee, em seguida, agregar os resultados usandofilter/sum.
❓ Perguntas Frequentes
P: Qual é a diferença entre
iter(),into_iter()eiter_mut()? R: Elas retornam tipos diferentes de iteradores.iter()retorna&T(uma referência imutável) sem transferir a propriedade;into_iter()retornaT(transferência de propriedade), o que consome a coleção original;iter_mut()retorna&mut T(referência mutável), o que permite que os elementos sejam modificados. O loopforusainto_iter()por padrão.
P: Por que a cadeia de adaptadores não gera um erro, mesmo sem executar nenhum código? R: Porque os adaptadores são preguiçosos. Os adaptadores apenas criam um “plano de operação” sem executá-lo. É como ter todas as máquinas instaladas em uma fábrica, mas desligadas — elas só começam a funcionar quando você chama um consumidor (
collect(),sum(), etc.). Essa é uma característica central do design de iteradores do Rust: abstração sem sobrecarga que realiza cálculos apenas quando realmente necessário.
P:
collect()Como sei qual tipo coletar? R: Por meio da inferência de tipo. É preciso especificar o tipo de destino, geralmente usando a sintaxe do TurboFish:.collect::<Vec<i32>>()ou declarando o tipo da variávellet v: Vec<i32> = iter.collect();. O compilador determina como coletar com base na implementação do tipo de destinoFromIterator.
P: O que é
type Itemem um iterador personalizado? R:Itemé um tipo associado que especifica o tipo deSomeretornado pornext(). Por exemplo,type Item = u64emIterator for Fibonacciindica quenext()retornaOption<u64>a cada vez. Os tipos associados permitem que você especifique o tipo dos elementos produzidos por um iterador sem a necessidade de parâmetros genéricos adicionais.
P: Qual é a diferença entre
fold()ereduce()? R:fold()requer um valor inicial, enquantoreduce()usa o primeiro elemento como valor inicial.fold(0, \|acc, x\| acc + x)começa a contar a partir de 0;reduce(\|acc, x\| acc + x)começa a contar a partir do primeiro elemento.fold()sempre retorna o tipo de valor inicial que você especificar, enquantoreduce()retornaOption<Self::Item>(ouNonese o iterador estiver vazio).
📖 Resumo
- A característica Iterator é a base do sistema de iteradores do Rust; basta implementar o método
next(), que retornaOption<Self::Item>. - Adaptadores de iteradores (
map,filter,take,skip,chain) são preguiçosos — eles apenas registram operações e não realizam cálculos. - Consumidores (
collect,sum,count,fold) são os pontos finais do pipeline — chamá-los aciona o cálculo propriamente dito - Chamadas encadeáveis transformam o código de processamento de dados de “como fazer” (imperativo) para “o que você quer” (declarativo), tornando-o mais claro e mais fácil de combinar
- Iteradores personalizados precisam apenas implementar
Iterator trait; uma vez implementados, eles passam automaticamente a ter acesso a todos os métodos do adaptador. - A avaliação preguiçosa é um princípio fundamental do projeto de iteradores — uma abstração sem sobrecarga que calcula valores somente quando necessário, evitando alocações intermediárias desnecessárias.
📝 Exercícios
-
Dificuldade ⭐: Reescreva o código a seguir usando iteradores. Extraia todos os números ímpares de
[1, 2, 3, 4, 5, 6, 7, 8], multiplique-os por 10, armazene-os em um Vec e imprima-os.RUST// Replace this loop with a chain of iterators let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8]; let mut result = vec![]; for &n in &numbers { if n % 2 == 1 { result.push(n * 10); } } println!("{:?}", result); -
Dificuldade ⭐⭐: Implemente a característica
Iteratorparastruct StepRange { start: i32, end: i32, step: i32 }de forma que ela possa ser usada comofor n in StepRange::new(0, 10, 2), gerando os valores 0, 2, 4, 6, 8 e 10. Em seguida, usemappara elevar cada valor ao quadrado e usecollectpara reuni-los em um Vec. -
Dificuldade ⭐⭐⭐: Escreva uma função
fn word_count(text: &str) -> std::collections::HashMap<String, usize>que utilize métodos de iterador para contar o número de vezes que cada palavra aparece em um texto. Requisitos: Usesplit_whitespace()para dividir o texto,mappara converter para letras minúsculas efoldpara construir um HashMap. Dica: a APIentry()doHashMap, combinada comor_insert(), facilita a contagem.