iter vs into_iter: batalha dos titãs da iteração em Rust
\n\nSe você já escreveu um loop sobre um vetor em Rust, certamente já encontrou os três irmãos gêmeos: iter(), into_iter() e iter_mut(). À primeira vista, todos fazem a mesma coisa — permitem percorrer os elementos. Mas a diferença entre eles é mais profunda do que parece, e está no coração do Rust — o sistema de propriedade.
Imagine que você é um barista e sua coleção são os grãos de café. iter() permite que você olhe para os grãos, os cheire, mas não os toque. into_iter() é quando você pega os grãos, os moe e faz um espresso, após o que os grãos desaparecem. E iter_mut() — você pode não apenas olhar, mas também moer os grãos diretamente no saco, mudando sua forma.
Hoje vamos analisar iter vs into_iter da forma como é explicado no Stack Overflow, mas com exemplos vivos e um mergulho profundo no capô. Vamos lá!
\n\nO que é um iterador em Rust?
\n\nUm iterador é um objeto que sabe como obter elementos sequencialmente de uma coleção. Em Rust, os iteradores são preguiçosos: eles não fazem nada até que você chame next(). Mas o principal é que cada método de iteração lida com a propriedade dos dados de forma diferente.
Aqui estão as três principais abordagens:
\n- \n
iter()— toma emprestado os elementos (não assume a propriedade) \n into_iter()— consome a coleção (assume a propriedade) \n iter_mut()— toma emprestado com possibilidade de modificação \n
Vamos olhar o código. Suponha que temos um vetor de nomes:
\n\nlet names = vec!["Ana".to_string(), "Boris".to_string(), "Viktor".to_string()];\n\nExemplo 1: iter() — apenas olhar
\n\nfn main() {\n let names = vec!["Ana".to_string(), "Boris".to_string(), "Viktor".to_string()];\n \n // iter() retorna &String (referência)\n for name in names.iter() {\n println!("Olá, {}!", name);\n }\n \n // names ainda está disponível!\n println!("Vetor vivo: {:?}", names);\n}\n\nObserve: após o loop, usamos names tranquilamente. Por quê? Porque iter() retornou uma referência para cada elemento. A propriedade permaneceu com o vetor. Isso é seguro e previsível.
Exemplo 2: into_iter() — queimar as pontes
\n\nfn main() {\n let names = vec!["Ana".to_string(), "Boris".to_string(), "Viktor".to_string()];\n \n // into_iter() retorna String (propriedade é movida)\n for name in names.into_iter() {\n println!("Adeus, {}!", name);\n }\n \n // Erro! names não existe mais\n // println!("Vetor vivo: {:?}", names); // ❌ borrow of moved value\n}\n\nAqui está a diferença chave. into_iter() consome a coleção. Cada elemento é movido para o loop, e o vetor original se torna inválido. Se você precisa manter a coleção para uso posterior — into_iter() não é sua escolha.
Exemplo 3: iter_mut() — modificar sem destruir
\n\nfn main() {\n let mut names = vec!["Ana".to_string(), "Boris".to_string(), "Viktor".to_string()];\n \n // iter_mut() retorna &mut String (referência mutável)\n for name in names.iter_mut() {\n name.push_str(" (convidado)");\n }\n \n println!("Vetor modificado: {:?}", names);\n // Saída: ["Ana (convidado)", "Boris (convidado)", "Viktor (convidado)"]\n}\n\nAqui, modificamos os elementos no local, mas não assumimos a propriedade. Ideal para transformações.
\n\nSob o capô: o que cada método retorna?
\n\nPara entender verdadeiramente iter vs into_iter, vamos dar uma olhada na biblioteca padrão. Para o vetor Vec<T>:
- \n
iter()retornastd::slice::Iter<T>— implementaIterator<Item = &T>