Les itérateurs en Rust
Un itérateur, c’est tout ce qui peut produire une suite de valeurs, une à la fois. Vous en utilisez depuis la page For sans forcément les nommer : chaque boucle for sur une collection repose en coulisses sur un itérateur.
Obtenir un itérateur
Section intitulée « Obtenir un itérateur »fn main() { let numbers = vec![1, 2, 3]; let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(3) println!("{:?}", iter.next()); // None}Sortie :
Some(1)Some(2)Some(3)None.next() renvoie une Option : Some(valeur) pour chaque élément, puis None quand il n’y en a plus. Une boucle for ne fait rien d’autre qu’appeler .next() encore et encore, jusqu’à obtenir None.
map : transformer chaque élément
Section intitulée « map : transformer chaque élément ».map() prend une closure (fonction anonyme) et l’applique à chaque élément, ce qui produit un nouvel itérateur :
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}Sortie :
[2, 4, 6, 8, 10]C’est .collect() qui retransforme l’itérateur en une vraie collection (ici, un Vec<i32>). Sans lui, vous auriez simplement un itérateur qui attend, sans que rien ne soit calculé.
filter : ne garder que certains éléments
Section intitulée « filter : ne garder que certains éléments »fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);}Sortie :
[2, 4, 6]Le double **n a l’air étrange. .filter() donne à la closure une référence vers une référence (&&i32), il faut donc la déréférencer deux fois pour la comparer à un nombre ordinaire. C’est une petite bizarrerie courante ; ne vous inquiétez pas encore de l’explication complète.
Enchaîner map et filter
Section intitulée « Enchaîner map et filter »C’est là que l’enchaînement des méthodes d’itérateurs devient vraiment intéressant : vous pouvez combiner plusieurs étapes en un seul « pipeline » facile à lire :
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|n| **n % 2 == 0) // garder les nombres pairs .map(|n| n * 10) // multiplier chacun par 10 .collect();
println!("{:?}", result);}Sortie :
[20, 40, 60]Comparez avec la même logique écrite à la main : une boucle for et un Vec vide dans lequel vous faites des push. Ce n’est pas faux, mais la version enchaînée se lit presque comme une phrase : « filtrer, puis multiplier, puis collecter ». C’est exactement la suite d’étapes que vous décrivez.
Quelques autres méthodes courantes
Section intitulée « Quelques autres méthodes courantes »fn main() { let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum(); let count = numbers.iter().count(); let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("somme : {total}, nombre : {count}, au moins un pair : {has_even}");}Sortie :
somme : 15, nombre : 5, au moins un pair : true.sum() additionne tout, .count() vous dit combien il y a d’éléments, et .any() répond à la question « est-ce qu’au moins un élément correspond ? ». Ce ne sont que quelques-unes des méthodes que possède chaque itérateur. Il y en a beaucoup d’autres, comme enumerate, zip, take ou rev. À la page Itérateurs personnalisés, vous construirez votre propre itérateur et vous verrez qu’il obtient toutes ces méthodes gratuitement.
Exercices
Section intitulée « Exercices »1. La somme des carrés impairs
Section intitulée « 1. La somme des carrés impairs »Prenez les nombres de 1 à 10, gardez seulement les impairs, élevez chacun au carré, puis additionnez le tout. Faites-le en une seule chaîne d’itérateurs, sans aucune variable mut. Indice : enchaînez .filter(), .map() et .sum(), et donnez un type au résultat, par exemple let total: i32.
Voir la solution
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers .iter() .filter(|n| **n % 2 == 1) // garde 1, 3, 5, 7, 9 .map(|n| n * n) // 1, 9, 25, 49, 81 .sum();
println!("somme des carrés impairs : {total}");}Sortie :
somme des carrés impairs : 165Rust a besoin du : i32 sur total, car .sum() peut produire plusieurs types de nombres différents, et il ne peut pas deviner celui que vous voulez.
2. Crier les mots longs
Section intitulée « 2. Crier les mots longs »Partez de vec!["the", "quick", "brown", "fox", "jumps"]. Construisez un nouveau Vec<String> qui ne contient que les mots de plus de 3 lettres, en majuscules, et affichez-le avec {:?}. Indice : .filter(|w| w.len() > 3) garde les mots longs, et .to_uppercase() transforme un &str en String en majuscules.
Les mots restent en anglais ici, car .len() compte des octets : une lettre accentuée en prend 2, ce qui fausserait le résultat.
Voir la solution
fn main() { let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words .iter() .filter(|w| w.len() > 3) .map(|w| w.to_uppercase()) .collect();
println!("{:?}", loud);}Sortie :
["QUICK", "BROWN", "JUMPS"]words lui-même ne change pas. .iter() ne fait que l’emprunter, vous pouvez donc toujours utiliser words après la chaîne.
Récapitulatif
Section intitulée « Récapitulatif »- Un itérateur produit des valeurs une par une grâce à
.next(), qui renvoie uneOption. - En coulisses, les boucles
forreposent sur des itérateurs. .map()transforme chaque élément ;.filter()ne garde que ceux qui remplissent une condition ;.collect()retransforme le résultat en une vraie collection.- Enchaîner des méthodes d’itérateurs est généralement plus clair qu’une boucle écrite à la main avec un accumulateur mutable.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Que renvoie .next() sur un itérateur ?
2. Que fait .collect() à la fin d'une chaîne comme .iter().map(...).collect() ?
3. Dans .filter(|n| **n % 2 == 0), pourquoi n est-il déréférencé deux fois ?
4. Qu'est-ce qui fait tourner une boucle for quand elle parcourt une collection ?
5. Que garde .filter() dans l'itérateur obtenu ?
Score : 0 / 5
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Processing a Series of Items with Iterators — The Rust Book (en anglais) : l’introduction officielle à
next,map,filteretcollect, avec d’autres exemples. - Comparing Performance: Loops vs. Iterators — The Rust Book (en anglais) : pourquoi une chaîne d’itérateurs est aussi rapide qu’une boucle écrite à la main.
- Le trait
Iterator— documentation de la bibliothèque standard (en anglais) : la liste complète des méthodes d’itérateurs, chacune avec un petit exemple. Idéal à parcourir. - Iterators — Rust by Example (en anglais) : un court exemple exécutable qui construit un itérateur de Fibonacci.