Por que retornar um iterador é um problema
\n\nVocê 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".
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\nO 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\nO problema: traits não têm tamanho
\n\nEm 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.
Portanto, não é possível retornar diretamente um Iterator. É necessário escolher um dos padrões:
- \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
Cada abordagem tem seu custo. Vamos analisá-las com exemplos reais.
\n\nMétodo 1: impl Iterator — o padrão ouro
\n\nA 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".
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\nPró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
Contras:
\n- \n
- O tipo retornado deve ser único — não é possível retornar
Mapem um caso eFilterem 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
Este método é ideal para 90% dos casos. Se sua função sempre retorna a mesma cadeia de iteradores — use impl Iterator sem hesitar.
Método 2: Box<dyn Iterator> — flexibilidade ao custo de desempenho
\n\nQuando 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.
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