iter vs into_iter : le choc des titans de l'itération en Rust
\n\nSi vous avez déjà écrit une boucle sur un vecteur en Rust, vous avez sûrement rencontré les trois frères jumeaux : iter(), into_iter() et iter_mut(). À première vue, ils font tous la même chose : permettre de parcourir les éléments. Mais la différence entre eux est plus profonde qu'il n'y paraît, et elle se trouve au cœur même de Rust : le système de propriété.
Imaginez que vous êtes un barista et que votre collection est un ensemble de grains de café. iter() vous permet de regarder les grains, de les sentir, mais pas de les toucher. into_iter(), c'est comme si vous preniez les grains, les mouliez et faisiez un espresso, après quoi les grains disparaissent. Et iter_mut(), vous pouvez non seulement regarder, mais aussi moudre les grains directement dans le sac, en changeant leur forme.
Aujourd'hui, nous allons décortiquer iter vs into_iter comme on l'explique sur Stack Overflow, mais avec des exemples concrets et une plongée en profondeur sous le capot. C'est parti !
\n\nQu'est-ce qu'un itérateur en Rust ?
\n\nUn itérateur est un objet qui sait comment obtenir séquentiellement des éléments d'une collection. En Rust, les itérateurs sont paresseux : ils ne font rien tant que vous n'appelez pas next(). Mais le principal est que chaque méthode d'itération traite la propriété des données différemment.
Voici les trois principales approches :
\n- \n
iter()— emprunte les éléments (ne prend pas la propriété) \n into_iter()— consomme la collection (prend la propriété) \n iter_mut()— emprunte avec possibilité de modification \n
Regardons le code. Supposons que nous ayons un vecteur de prénoms :
\n\nlet names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n\nExemple 1 : iter() — juste regarder
\n\nfn main() {\n let names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n \n // iter() retourne &String (une référence)\n for name in names.iter() {\n println!("Bonjour, {} !", name);\n }\n \n // names est toujours accessible !\n println!("Le vecteur est vivant : {:?}", names);\n}\n\nRemarquez qu'après la boucle, nous utilisons names sans problème. Pourquoi ? Parce que iter() a retourné une référence à chaque élément. La propriété reste au vecteur. C'est sûr et prévisible.
Exemple 2 : into_iter() — brûler les ponts
\n\nfn main() {\n let names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n \n // into_iter() retourne String (la propriété est déplacée)\n for name in names.into_iter() {\n println!("Adieu, {} !", name);\n }\n \n // Erreur ! names n'existe plus\n // println!("Le vecteur est vivant : {:?}", names); // ❌ emprunt d'une valeur déplacée\n}\n\nVoilà la différence clé. into_iter() consomme la collection. Chaque élément est déplacé dans la boucle, et le vecteur d'origine devient invalide. Si vous devez conserver la collection pour une utilisation ultérieure, into_iter() n'est pas votre choix.
Exemple 3 : iter_mut() — modifier sans détruire
\n\nfn main() {\n let mut names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n \n // iter_mut() retourne &mut String (une référence mutable)\n for name in names.iter_mut() {\n name.push_str(" (invité)");\n }\n \n println!("Vecteur modifié : {:?}", names);\n // Sortie : ["Anna (invité)", "Boris (invité)", "Victor (invité)"]\n}\n\nIci, nous modifions les éléments sur place, sans prendre la propriété. Idéal pour les transformations.
\n\nSous le capot : que retourne chaque méthode ?
\n\nPour vraiment comprendre iter vs into_iter, regardons dans la bibliothèque standard. Pour un vecteur Vec<T> :
- \n
iter()retournestd::slice::Iter<T>— implémenteIterator<Item = &T>