Retornando iteradores em Rust: segredos de desempenho e abstrações de custo zero

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que retornar um iterador é um problema

\n\n

Você já escreveu uma função em Rust que precisa retornar uma coleção processada através de uma cadeia de iteradores? Se sim, você certamente já se deparou com a pergunta: "Qual tipo devo retornar?" Parece lógico escrever fn get_data() -> Iterator, mas o compilador imediatamente emite um erro: "the trait `Iterator` cannot be made into an object".

\n\n

Este problema é tão comum que no Stack Overflow a pergunta "What is the correct way to return an Iterator (or any other trait)?" acumulou milhares de visualizações. E não é surpresa — a escolha correta afeta não apenas a conveniência do código, mas também o desempenho. Neste artigo, analisaremos todas as formas de retornar iteradores em Rust, seus prós e contras e, principalmente, como não perder desempenho à toa.

\n\n

O artigo foi escrito para pythonlib.ru — um site educacional onde não temos medo de olhar sob o capô de linguagens populares. Rust, com suas abstrações de custo zero, nos dá a oportunidade única de escrever código que é ao mesmo tempo elegante e rápido. Mas apenas se soubermos usar suas ferramentas corretamente.

\n\n

O problema: traits não têm tamanho

\n\n

Em Rust, traits são interfaces, mas ao contrário de Java ou Go, não são tipos com tamanho conhecido. O compilador precisa saber quanta memória o valor retornado ocupa para alocar espaço na pilha. E o tamanho de uma implementação específica de iterador (por exemplo, std::iter::Map ou std::iter::Filter) pode ser diferente.

\n\n

Portanto, não é possível retornar diretamente um Iterator. É necessário escolher um dos padrões:

\n\n
    \n
  • Despacho estático — impl Iterator (retorno de tipo concreto)
  • \n
  • Despacho dinâmico — Box<dyn Iterator> (tipo apagado)
  • \n
  • Enum dispatch — seu próprio enum para todos os iteradores possíveis
  • \n
  • Genéricos — função que aceita e retorna tipos genéricos
  • \n
\n\n

Cada abordagem tem seu custo. Vamos analisá-las com exemplos reais.

\n\n

Método 1: impl Iterator — o padrão ouro

\n\n

A maneira mais simples e eficiente é usar impl Trait no tipo de retorno. Isso é açúcar sintático que diz ao compilador: "vou retornar algum tipo concreto que implementa a trait, mas você mesmo o deduz".

\n\n
fn get_numbers() -> impl Iterator<Item = i32> {\n    (0..10).map(|x| x * 2)\n}\n\nfn main() {\n    for num in get_numbers() {\n        println!("{}", num);\n    }\n}
\n\n

Prós:

\n
    \n
  • Máximo desempenho — despacho estático, sem chamadas virtuais
  • \n
  • O compilador pode inline o código e aplicar otimizações
  • \n
  • Tamanho mínimo do binário resultante
  • \n
\n\n

Contras:

\n
    \n
  • O tipo retornado deve ser único — não é possível retornar Map em um caso e Filter em outro
  • \n
  • O tipo pode ser muito complexo (ex.: Map<Filter<...>>), dificultando a leitura de erros do compilador
  • \n
  • Não pode ser armazenado em uma struct ou vetor — apenas em funções
  • \n
\n\n

Este método é ideal para 90% dos casos. Se sua função sempre retorna a mesma cadeia de iteradores — use impl Iterator sem hesitar.

\n\n

Método 2: Box<dyn Iterator> — flexibilidade ao custo de desempenho

\n\n

Quando você precisa retornar diferentes tipos de iteradores dependendo de condições (por exemplo, ao analisar argumentos de linha de comando), o despacho dinâmico através de Box<dyn Iterator> vem ao resgate.

\n\n
fn get_data(use_filter: bool) -> Box<dyn Iterator<Item = i32>> {\n    let data = vec![1, 2, 3, 4, 5];\n    if use_filter {\n                

Blogs

Book Recommendations