Como percorrer números com passo 3, 5 ou 100 em Rust? Análise completa
Imagine: você está programando em Rust e precisa exibir todos os números pares de 0 a 20. Ou, digamos, construir a sequência 0, 5, 10, 15, 20. Em Python, você simplesmente escreveria range(0, 21, 5) — e pronto. E como fazer em Rust? Aqui há nuances, e hoje vamos analisá-las.
Esta pergunta é uma das mais populares no Stack Overflow (milhares de visualizações). E não é surpresa: o padrão for i in 0..10 funciona apenas com passo 1. E se precisar de um passo diferente? Acontece que existem três maneiras, cada uma com seus prós e armadilhas.
Não vamos apenas mostrar o código — vamos comparar as abordagens em termos de desempenho, legibilidade e flexibilidade. Vamos lá!
Por que o Rust não tem um range embutido com passo?
Resposta curta: porque Range em Rust é um iterador simples que gera uma sequência de números inteiros. Ele não suporta passo por padrão para permanecer o mais leve e previsível possível. Mas os desenvolvedores forneceram métodos que resolvem esse problema.
Resposta longa: no ecossistema Rust, a filosofia é "explícito é melhor que implícito". Em vez de incorporar o passo na sintaxe do intervalo, os criadores propuseram ferramentas flexíveis de adaptação de iteradores. E isso abre mais possibilidades do que parece.
Método 1: step_by() — o caminho mais direto
Na biblioteca padrão do Rust, existe o método step_by(n), que cria um iterador pulando a cada n-1 elementos. Veja como funciona:
fn main() { // Exibe números de 0 a 20 com passo 5 for i in (0..=20).step_by(5) { println!("{}", i); } // Saída: 0, 5, 10, 15, 20}Observe: usei 0..=20 (inclusivo), não 0..20 (exclusivo). Se precisar de passo 3 até 10, escreva (0..10).step_by(3) — obterá 0, 3, 6, 9.
Vantagens do step_by():
- Simplicidade — uma linha, sem closures
- Legibilidade — fica claro o que está sendo feito
- Desempenho — sobrecarga mínima
Desvantagens:
- Funciona apenas com números inteiros (i32, u64, etc.)
- Não é possível definir um passo dinâmico a cada iteração
- Surgiu apenas no Rust 1.28 (mas já faz tempo)
Este método é a escolha ideal para 90% das tarefas. Se você precisa apenas percorrer números com passo constante — use step_by().
Método 2: filter() — quando o passo é uma condição
E se o passo não for constante? Por exemplo, precisa exibir números que são divisíveis por 3 sem resto. Ou números onde a soma dos dígitos é maior que 5. É aí que o filter() entra em ação:
fn main() { // Todos os números de 0 a 30, múltiplos de 4 for i in (0..=30).filter(|x| x % 4 == 0) { println!("{}", i); } // Saída: 0, 4, 8, 12, 16, 20, 24, 28
// Agora — números que não são divisíveis por 3 for i in (0..20).filter(|x| x % 3 != 0) { println!("{}", i); } // Saída: 1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19}Aqui passamos uma closure que verifica cada número. filter() mantém apenas os elementos para os quais a condição é verdadeira.
Quando filter() é melhor que step_by():
- O passo depende do valor (ex.: "todos os números primos")
- Precisa combinar com outras condições
- O passo não é constante (ex.: números de Fibonacci)
Desvantagens:
- Um pouco mais lento que step_by() — é necessário verificar cada número
- O código pode ser menos óbvio para iniciantes
Pessoalmente, uso filter() quando preciso não apenas de "cada n-ésimo", mas de "cada um que atende a uma regra". É mais poderoso, mas requer mais atenção.