Iteradores em Rust
Um iterador é qualquer coisa que consegue produzir uma sequência de valores, um de cada vez. Você já usa iteradores desde a página do For, mesmo sem chamá-los por esse nome: todo laço for sobre uma coleção funciona com um iterador por baixo dos panos.
Obtendo um iterador
Seção intitulada “Obtendo um iterador”fn main() { let numbers = vec![1, 2, 3]; let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(3) println!("{:?}", iter.next()); // None}Saída:
Some(1)Some(2)Some(3)None.next() retorna um Option: Some(valor) para cada item e None quando os itens acabam. No fundo, um laço for só fica chamando .next() várias vezes até receber None.
map: transformar cada item
Seção intitulada “map: transformar cada item”.map() recebe uma closure (função anônima) e a aplica a cada item, gerando um novo iterador:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}Saída:
[2, 4, 6, 8, 10]É o .collect() que transforma o iterador de volta em uma coleção concreta (aqui, um Vec<i32>). Sem ele, você teria só um iterador parado ali, sem nada calculado ainda.
filter: ficar só com alguns itens
Seção intitulada “filter: ficar só com alguns itens”fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);}Saída:
[2, 4, 6]O **n duplo parece estranho. Acontece que o .filter() entrega para a closure uma referência para uma referência (&&i32), então é preciso desreferenciar duas vezes para comparar com um número comum. É um detalhe bem comum; não se preocupe com a explicação completa por enquanto.
Encadeando map e filter
Seção intitulada “Encadeando map e filter”É aqui que encadear métodos de iterador começa a valer a pena: você junta vários passos em um único fluxo fácil de ler:
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|n| **n % 2 == 0) // fica só com os números pares .map(|n| n * 10) // multiplica cada um por 10 .collect();
println!("{:?}", result);}Saída:
[20, 40, 60]Compare com escrever a mesma lógica usando um laço for manual e um Vec vazio onde você vai dando push. Não está errado, mas a versão encadeada se lê quase como “filtre, depois multiplique, depois junte”, que é exatamente a sequência de passos que você está descrevendo.
Outros métodos comuns
Seção intitulada “Outros métodos comuns”fn main() { let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum(); let count = numbers.iter().count(); let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("soma: {total}, quantidade: {count}, tem par: {has_even}");}Saída:
soma: 15, quantidade: 5, tem par: trueO .sum() soma tudo, o .count() diz quantos itens existem e o .any() responde à pergunta “pelo menos um item atende à condição?”. Esses são só alguns dos métodos que todo iterador tem. Existem muitos outros, como enumerate, zip, take e rev. Na página de Iteradores personalizados, você vai criar o seu próprio iterador e ver que ele ganha todos esses métodos de graça.
Prática
Seção intitulada “Prática”1. Soma dos quadrados ímpares
Seção intitulada “1. Soma dos quadrados ímpares”Pegue os números de 1 a 10, fique só com os ímpares, eleve cada um ao quadrado e some todos. Faça tudo em uma única cadeia de iteradores, sem nenhuma variável mut. Dica: encadeie .filter(), .map() e .sum(), e dê um tipo ao resultado, como let total: i32.
Ver solução
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers .iter() .filter(|n| **n % 2 == 1) // fica com 1, 3, 5, 7, 9 .map(|n| n * n) // 1, 9, 25, 49, 81 .sum();
println!("soma dos quadrados ímpares: {total}");}Saída:
soma dos quadrados ímpares: 165O Rust precisa do : i32 em total porque o .sum() consegue somar em vários tipos numéricos diferentes, e ele não tem como adivinhar qual você quer.
2. Grite as palavras longas
Seção intitulada “2. Grite as palavras longas”Comece com vec!["the", "quick", "brown", "fox", "jumps"]. Monte um novo Vec<String> só com as palavras de mais de 3 letras, em maiúsculas, e imprima-o com {:?}. Dica: .filter(|w| w.len() > 3) fica com as palavras longas, e .to_uppercase() transforma um &str em uma String em maiúsculas.
(As palavras ficam em inglês de propósito: o .len() conta bytes, e letras acentuadas como “ã” ou “é” ocupam 2 bytes, o que mudaria o resultado do filtro.)
Ver solução
fn main() { let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words .iter() .filter(|w| w.len() > 3) .map(|w| w.to_uppercase()) .collect();
println!("{:?}", loud);}Saída:
["QUICK", "BROWN", "JUMPS"]O words continua igual. O .iter() só o pega emprestado, então você ainda pode usar words depois da cadeia.
- Um iterador produz valores um de cada vez com
.next(), que retorna umOption. - Laços
forusam iteradores por baixo dos panos. .map()transforma cada item;.filter()fica só com os que atendem a uma condição;.collect()transforma o resultado de volta em uma coleção concreta.- Encadear métodos de iterador costuma ser mais claro do que um laço manual com um acumulador mutável.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que .next() retorna em um iterador?
2. O que .collect() faz no fim de uma cadeia como .iter().map(...).collect()?
3. Em .filter(|n| **n % 2 == 0), por que n é desreferenciado duas vezes?
4. O que faz um laço for funcionar quando ele percorre uma coleção?
5. O que o .filter() mantém no iterador resultante?
Pontuação: 0 / 5
Para saber mais
Seção intitulada “Para saber mais”- Processing a Series of Items with Iterators — The Rust Book (em inglês): a introdução oficial a
next,map,filterecollect, com mais exemplos. - Comparing Performance: Loops vs. Iterators — The Rust Book (em inglês): por que cadeias de iteradores são tão rápidas quanto um laço escrito à mão.
- A trait
Iterator— documentação da biblioteca padrão (em inglês): a lista completa de métodos de iterador, cada um com um pequeno exemplo. Vale a pena dar uma olhada. - Iterators — Rust by Example (em inglês): um exemplo curto e executável que cria um iterador de Fibonacci.