6. Closures, itérateurs et manipulation de chaînes

Les closures et les itérateurs sont deux des fonctionnalités les plus puissantes et élégantes de Rust. Combinés, ils permettent de transformer, filtrer et collecter des données de façon expressive et performante, sans allocation intermédiaire grâce à l’évaluation paresseuse (lazy evaluation). Dans cette unité, vous apprendrez également à manipuler les chaînes de caractères, un aspect fondamental pour tout utilitaire de traitement de texte.

 

Les closures : fonctions anonymes

Une closure est une fonction anonyme qui peut capturer des variables de son environnement. La syntaxe utilise des pipes :

fn main() {
    let multiplicateur = 3;

    // Closure qui capture multiplicateur
    let tripler = |x| x * multiplicateur;

    println!("{}", tripler(5));   // 15
    println!("{}", tripler(10));  // 30

    // Closure avec type explicite
    let additionner = |a: i32, b: i32| -> i32 { a + b };
    println!("{}", additionner(3, 4));  // 7
}

Les traits Fn, FnMut, FnOnce

Rust distingue trois traits pour les closures selon comment elles capturent leur environnement : Fn (emprunt immuable, peut être appelée plusieurs fois), FnMut (emprunt mutable, peut modifier les captures), et FnOnce (consomme la valeur capturée, ne peut être appelée qu’une fois).

 

Les itérateurs : traitement paresseux

Les itérateurs en Rust utilisent une évaluation paresseuse : les transformations sont enchaînées mais n’exécutent rien tant qu’on ne les consomme pas avec collect(), for_each(), ou un accumulateur. Cela permet des chaînes de transformation sans allocation intermédiaire.

let references = vec!["DOC-2024-001", "FACT-2023-042", "DOC-2024-007", "INV-2024-003"];

// Filtrer les DOC, extraire le numéro, collecter
let numeros_doc: Vec<u32> = references.iter()
    .filter(|r| r.starts_with("DOC"))
    .filter_map(|r| r.split("--").last())
    .filter_map(|n| n.parse::<u32>().ok())
    .collect();

println!("{:?}", numeros_doc);

Les méthodes itérateurs essentielles

let nombres = vec![1, 2, 3, 4, 5, 6];

// map : transformer chaque élément
let doubles: Vec<i32> = nombres.iter().map(|&x| x * 2).collect();

// filter : garder les éléments satisfaisant un prédicat
let pairs: Vec<&i32> = nombres.iter().filter(|&&x| x % 2 == 0).collect();

// fold/reduce : accumuler
let somme: i32 = nombres.iter().sum();
let produit: i32 = nombres.iter().fold(1, |acc, &x| acc * x);

// enumerate : obtenir index + valeur
for (i, val) in nombres.iter().enumerate() {
    println!("nombres[{}] = {}", i, val);
}

// zip : combiner deux itérateurs
let lettres = vec!["a", "b", "c"];
let paires: Vec<_> = nombres.iter().zip(lettres.iter()).collect();

 

String vs &str : la distinction fondamentale

Rust distingue deux types de chaînes : String (propriété, heap-alloué, modifiable) et &str (emprunt, slice d’une chaîne). Comprendre cette distinction est essentiel pour écrire du code idiomatique.

// &str : slice immuable, souvent des littéraux
let s1: &str = "bonjour";

// String : propriétaire, modifiable
let mut s2: String = String::from("bonjour");
s2.push_str(", monde");
s2.push('!');

// Convertir
let s3: String = s1.to_string();
let s4: &str = &s2;  // deref coercion

Méthodes de manipulation de chaînes

let ref_str = "  DOC-2024-042  ";

let propre = ref_str.trim();
let parties: Vec<&str> = propre.split("--").collect();

println!("{}", propre.starts_with("DOC"));   // true
println!("{}", propre.contains("2024"));     // true
println!("{}", propre.to_uppercase());

let reformate = propre.replace("--", "/");
let rejointe = parties.join("_");
Points clés :

  • Les closures capturent leur environnement avec la syntaxe |args| corps
  • Les itérateurs sont paresseux : chaînez les transformations, consommez avec collect()
  • map, filter, filter_map, fold sont les méthodes itérateurs les plus utilisées
  • &str pour emprunter du texte, String pour en posséder — préférez &str en paramètre
SEE ALL Add a note
YOU
Add your Comment