ProgrammationDéveloppeur Rust / Développeur Backend

Parlez-nous de la façon dont les itérateurs et les adaptateurs d'itérateurs fonctionnent en Rust. Quelle est la différence entre l'implémentation d'un itérateur personnalisé et l'utilisation d'adaptateurs standard, et dans quels cas est-il pertinent de mettre en œuvre un itérateur personnalisé ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En Rust, toute la bibliothèque standard de collections est basée sur le concept d'itérateurs. Un itérateur est un objet qui implémente le trait Iterator, dans lequel la méthode next() est définie. Cette méthode renvoie le prochain élément d'une séquence de données (Option<T>, où Some(T) est la valeur suivante, None est la fin de la séquence).

Exemple d'itérateur standard :

let v = vec![1, 2, 3]; let mut iter = v.iter(); while let Some(x) = iter.next() { println!("{}", x); }

Adaptateurs d'itérateurs – ce sont des méthodes (par exemple, .map(), .filter(), .enumerate(), .take()) qui retournent de nouveaux itérateurs, traitant les valeurs "à la volée" selon les fonctions passées.

Itérateurs personnalisés sont mis en œuvre en créant une structure et en implémentant le trait Iterator pour elle avec un comportement propre:

struct Counter { count: u8 } impl Iterator for Counter { type Item = u8; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }

Utilisez des adaptateurs standard lorsque ceux-ci suffisent pour traiter les collections. L'implémentation d'un itérateur personnalisé est nécessaire si :

  • Il est nécessaire de représenter une séquence générée algorithmiquement ou paresseusement.
  • Un contrôle approfondi du processus est requis.
  • Une intégration avec des sources de données externes/non standard est nécessaire.

Question piège

Peut-on créer des itérateurs infinis ? Que se passe-t-il si l'on essaie de les collecter dans une collection, par exemple avec .collect() ?

Réponse : Oui, en Rust, des itérateurs comme std::iter::repeat existent, retournant une séquence infinie:

let mut endless = std::iter::repeat(1); endless.next(); // retournera Some(1) indéfiniment

Si on tente de collecter un tel itérateur dans une collection via .collect(), le programme se bloquera ou échouera avec un débordement de mémoire, car l'itération ne se terminera pas d'elle-même !

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un projet REST API, un tableau de 1000 éléments était trié, puis .iter().filter(|x| *x > 500) était utilisé, mais au lieu de .collect::<Vec<_>>(), .fold(0, |acc, _| acc + 1) à l'intérieur d'un adaptateur complexe était appliqué, perdant la compréhension de la manière dont l'itération se terminerait correctement. Résultat : des blocages aléatoires dus au fait que la filtration se produisait par la paresse illimitée de l'itérateur avec un bug interne.


Histoire

Dans un moteur propriétaires pour générer des identifiants uniques, un développeur aléatoire a décidé de mettre en œuvre son itérateur, oubliant de retourner None lors de l'atteinte de la limite. En conséquence, l'itération est devenue une boucle infinie, le serveur en production utilisait tout le CPU et ne répondait pas aux requêtes.


Histoire

Dans la partie frontend (module WebAssembly), un itérateur avec des adaptateurs imbriqués était utilisé : .map().filter().skip(), et ils essayaient d'obtenir le résultat via .collect() pour un formulaire. Lorsqu'ils ont modifié le type sous l'adaptateur, Rust a généré une erreur complexe à la compilation sur une incompatibilité de types, car ils avaient oublié de spécifier le type exact de la collection : .collect::<Vec<_>>(). Le problème a été résolu en ajoutant une annotation, mais ils ont perdu plusieurs heures à chercher la cause.