4. Itérateurs et Closures
Les itérateurs en Rust sont paresseux et composables. Combinés aux closures, ils permettent d’écrire du code de traitement de données concis, sûr et performant (zéro-coût par rapport à une boucle manuelle).
4.1 Closures
Une closure est une fonction anonyme qui peut capturer son environnement.
Syntaxe
let add_one = |x: i32| -> i32 { x + 1 };
let add_one = |x| x + 1; // types inférés
let add_one = |x| x + 1; // encore plus court
println!("{}", add_one(41)); // 42Capture de l’environnement
Les closures peuvent capturer des variables de leur portée. Le compilateur choisit automatiquement la méthode de capture la moins restrictive.
let factor = 2.0;
let scale = |x: f64| x * factor; // capture factor par référence (&f64)
let mut values = vec![1, 2, 3];
let mut push = |v| values.push(v); // capture par référence mutable (&mut Vec)
let s = String::from("hello");
let consume = || drop(s); // capture par move (s est déplacé)
// println!("{s}"); // ⛕ s a été moved dans la closure
// Forcer le move avec le mot-clé move :
let data = vec![1, 2, 3];
let handler = move || { // data est moved dans la closure
println!("{:?}", data);
};Closures et fonctions
Une closure qui ne capture rien peut être utilisée comme une fonction :
fn add_one_fn(x: i32) -> i32 { x + 1 }
let add_one_closure = |x| x + 1;
fn apply(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }
apply(add_one_fn, 41); // ✅ fn pointer
apply(add_one_closure, 41); // ✅ aussi (si pas de capture)4.2 Itérateurs
Un itérateur est n’importe quel type qui implémente le trait Iterator :
pub trait Iterator {
type Item; // type des éléments produits
fn next(&mut self) -> Option<Self::Item>; // méthode obligatoire
}Créer un itérateur
// Sur les collections
let v = vec![1, 2, 3];
let iter = v.iter(); // &T (références immutables)
let iter = v.iter_mut(); // &mut T (références mutables)
let iter = v.into_iter(); // T (consomme la collection)
// Plages
let iter = 0..10;
let iter = 0..=10; // inclusif (0 à 10)
// FromIterator (collect)
let vec: Vec<i32> = (0..5).collect();Adaptateurs paresseux
Les adaptateurs transforment un itérateur en un autre itérateur. Ils sont paresseux — rien n’est exécuté tant qu’on n’appelle pas next() ou collect().
let v = vec![1, 2, 3, 4, 5];
// map : transforme chaque élément
let doubled: Vec<i32> = v.iter()
.map(|x| x * 2)
.collect();
// filter : garde les éléments selon un prédicat
let evens: Vec<&i32> = v.iter()
.filter(|x| *x % 2 == 0)
.collect();
// filter_map : combinaison filter + map (Option)
// Utile pour .ok() : Result → Option
let strings = vec!["42", "hello", "37"];
let parsed: Vec<i32> = strings.iter()
.filter_map(|s| s.parse().ok())
.collect(); // [42, 37]
// flat_map : chaque élément produit un itérateur, aplati
let words = vec!["hello world", "rust is cool"];
let all_words: Vec<&str> = words.iter()
.flat_map(|s| s.split_whitespace())
.collect(); // ["hello", "world", "rust", "is", "cool"]
// take et skip
let first_3: Vec<&i32> = v.iter().take(3).collect(); // [1, 2, 3]
// enumerate
for (i, val) in v.iter().enumerate() {
println!("index {i}: {val}");
}
// zip : combine deux itérateurs
let keys = vec!["a", "b", "c"];
let values = vec![1, 2, 3];
let pairs: Vec<(&str, i32)> = keys.iter()
.zip(values.iter())
.map(|(k, v)| (*k, *v))
.collect(); // [("a", 1), ("b", 2), ("c", 3)]Consommateurs d’itérateurs
let v = vec![1, 2, 3, 4, 5];
// sum, product
let sum: i32 = v.iter().sum();
let product: i32 = v.iter().product();
// count, max, min
let count = v.iter().count();
let max = v.iter().max(); // Option<&i32>
let min = v.iter().min();
// any, all (prédicats)
let has_even = v.iter().any(|x| x % 2 == 0);
let all_positive = v.iter().all(|x| x > 0);
// fold (réduction générale)
let sum = v.iter().fold(0, |acc, x| acc + x);
// position, rposition
let pos = v.iter().position(|x| x == &3); // Option<usize>
// find
let found = v.iter().find(|x| **x > 3); // Option<&i32>4.3 Exemple Concret : Médiane sur Itérateur
fn median(gradients: &[f64]) -> Option<f64> {
let n = gradients.len();
if n == 0 {
return None;
}
// Trier avec un itérateur (sans copie inutile)
let mut sorted: Vec<f64> = gradients.iter().copied().collect();
sorted.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
let mid = n / 2;
if n % 2 == 0 {
Some((sorted[mid - 1] + sorted[mid]) / 2.0)
} else {
Some(sorted[mid])
}
}
fn trimmed_mean(gradients: &[f64], trim_ratio: f64) -> Option<f64> {
let n = gradients.len();
if n == 0 {
return None;
}
let trim_count = (n as f64 * trim_ratio).ceil() as usize;
// Version itérateur
let mut sorted: Vec<f64> = gradients.iter().copied().collect();
sorted.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
let sum: f64 = sorted.iter()
.skip(trim_count) // ignore les plus petits
.take(n - 2 * trim_count) // prend la partie centrale
.sum();
let count = n - 2 * trim_count;
if count == 0 { None } else { Some(sum / count as f64) }
}4.4 Itérateurs Personnalisés
struct GradientIter<'a> {
grads: &'a [f64],
index: usize,
}
impl<'a> Iterator for GradientIter<'a> {
type Item = &'a f64;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.grads.len() {
None
} else {
let result = &self.grads[self.index];
self.index += 1;
Some(result)
}
}
}
// Convertir un slice en itérateur
fn iter_gradients(grads: &[f64]) -> GradientIter<'_> {
GradientIter { grads, index: 0 }
}
// Test
let g = vec![1.0, 2.0, 3.0];
for val in iter_gradients(&g) {
println!("{val}");
}4.5 Itérateurs et Performance (Zéro-Cost Abstraction)
Les itérateurs Rust se compilent en code machine identique à une boucle for manuelle. Pas de surcoût.
// Boucle manuelle
let mut sum = 0;
for i in 0..1000 {
sum += i;
}
// Itérateur (même code assembleur !)
let sum: i32 = (0..1000).sum();Benchmark : v.iter().map(|x| x * 2).filter(|x| x > 10).sum() produit le même assembleur que la boucle for équivalente. Pas d’allocation, pas de virtual call.
4.6 Résumé
| Adaptateur | Effet | Paresseux |
|---|---|---|
.map(f) | Transforme chaque élément | ✅ |
.filter(p) | Garde selon prédicat | ✅ |
.filter_map(f) | map + filter en un passage | ✅ |
.flat_map(f) | Chaque élément → itérateur, aplati | ✅ |
.take(n) | Garde les n premiers | ✅ |
.skip(n) | Ignore les n premiers | ✅ |
.zip(other) | Combine deux itérateurs | ✅ |
.enumerate() | Ajoute l’index | ✅ |
.chain(other) | Concatène deux itérateurs | ✅ |
.cycle() | Répète à l’infini | ✅ |
| Consommateur | Effet |
|---|---|
.collect() | Produit une collection |
.sum() / .product() | Somme / produit |
.fold(init, f) | Réduction générale |
.any() / .all() | Test booléen |
.count() / .max() / .min() | Statistiques |