Programmation fonctionnelle en Rust : niveau avancé
\n\nRust est un langage multi-paradigme qui combine harmonieusement les styles impératif, orienté objet et fonctionnel. À un niveau avancé, la programmation fonctionnelle en Rust permet d'écrire un code plus sûr, plus expressif et plus concis. Dans cet article, nous aborderons les concepts clés : les closures avec capture d'environnement, les itérateurs avancés, les foncteurs et les monades avec les exemples de Option et Result, ainsi que la curryfication et la composition de fonctions.
1. Closures : possession, emprunt et mutabilité
\n\nLes closures en Rust ne sont pas de simples fonctions anonymes. Elles peuvent capturer des variables de la portée environnante de trois manières : par référence (&T), par référence mutable (&mut T) et par valeur (T). Le compilateur choisit automatiquement le trait le moins contraignant : Fn, FnMut ou FnOnce.
Considérons un exemple avec capture par valeur et utilisation de move :
fn main() {\n let data = vec![1, 2, 3];\n \n // Capture par valeur (move) — la possession est transférée à la closure\n let closure = move || {\n println!("{:?}", data);\n // data n'est plus accessible dans la fonction principale\n };\n \n closure();\n // println!("{:?}", data); // Erreur : data a été déplacée\n}\n\n\nUne technique avancée consiste à utiliser les closures pour créer des fabriques de fonctions :
\n\nfn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {\n move |x| x * factor\n}\n\nfn main() {\n let double = make_multiplier(2);\n let triple = make_multiplier(3);\n \n println!("{}", double(5)); // 10\n println!("{}", triple(5)); // 15\n}\n\n\nIci, impl Fn(i32) -> i32 est le type de retour de la closure. Rust génère automatiquement un type anonyme qui implémente le trait Fn.
2. Itérateurs avancés : chaînes, adaptateurs et évaluation paresseuse
\n\nLes itérateurs en Rust sont un outil puissant du style fonctionnel. Ils sont paresseux : les calculs n'ont lieu que lors de l'appel d'une méthode terminale (collect, for_each, fold, etc.).
Considérons un pipeline de traitement de données complexe :
\n\nfn process_data(data: Vec<i32>) -> Vec<i32> {\n data.iter()\n .filter(|&x| x > 0) // On garde les positifs\n .map(|x| x * 2) // On double\n .enumerate() // On ajoute les indices\n .filter(|(i, _)| i % 2 == 0) // Indices pairs\n .map(|(_, x)| x) // On extrait la valeur\n .collect() // Méthode terminale\n}\n\n\nUne technique avancée consiste à utiliser flat_map pour travailler avec des structures imbriquées et fold pour l'accumulation :
fn main() {\n let nested = vec![vec![1, 2], vec![3, 4, 5], vec![6]];\n \n let flattened: Vec<i32> = nested.iter()\n .flat_map(|v| v.iter())\n .copied()\n .collect();\n \n println!("{:?}", flattened); // [1, 2, 3, 4, 5, 6]\n \n // fold — l'équivalent fonctionnel de reduce\n let sum = flattened.iter()\n .fold(0, |acc, x| acc + x);\n \n println!("Somme : {}", sum); // 21\n}\n\n\nIl est important de se rappeler : les chaînes d'itérateurs ne créent pas de collections intermédiaires si collect n'est pas appelé. Cela économise de la mémoire et améliore les performances.
3. Monades Option et Result : gestion fonctionnelle des erreurs
\n\nEn programmation fonctionnelle, Option et Result sont des monades. Elles permettent de traiter en chaîne les valeurs optionnelles et les erreurs sans imbriquer des match ou des if let.
Considérons un scénario typique avec des vérifications imbriquées :
\n\nBlogs
Book Recommendations
Lire aussi
WebSocket en Java : applications temps réel avancées
Monorepo avec Swift : organisation du code pour les projets iOS et macOS
Travail avec le système de fichiers en C# : guide complet pour débutants
Code Review en TypeScript : 7 choses que je vérifie
Go : Fonctionnel ou POO ? Choisir son style de programmation