Style fonctionnel en Rust : Immutabilité, code déclaratif et gestion des erreurs
La programmation fonctionnelle en Rust résout les problèmes courants des styles impératif et orienté objet : la mutabilité, les boucles impératives et la gestion implicite des erreurs. Plutôt que de modifier l'état, le code est construit sur des transformations immuables, des opérations déclaratives et des types explicites comme Result et Option. Cela rend les programmes prévisibles et moins sujets aux erreurs.
Rust est immuable par défaut, encourageant les développeurs à repenser les modèles familiers. Le style fonctionnel utilise des itérateurs, la composition de fonctions et le filtrage par motif pour un code propre et sans effets de bord.
Immutabilité plutôt que mutation
Les variables mutables créent des dépendances cachées : les valeurs changent de manière inattendue, compliquant le débogage. Dans le code impératif, une variable mut x peut être modifiée à tout moment entre sa déclaration et son utilisation.
fn ma_fonction(num: i32) -> i32 {
let mut x = num;
// ...30 lignes supplémentaires...
x += 96;
// ...50 lignes supplémentaires...
x
}
Ici, la valeur d'origine est corrompue et le résultat est imprévisible en raison de modifications intermédiaires potentielles. L'immutabilité résout ce problème : de nouvelles valeurs sont créées au lieu de modifier les anciennes.
let x = 10;
let nouveau_x = traitement(x); // x reste inchangé
En Rust, l'immutabilité est la norme. La mutation nécessite un mut explicite, mais le style fonctionnel minimise son utilisation :
- Vérifiez si un problème peut être résolu de manière immuable en utilisant des itérateurs ou map.
- Si la mutation est inévitable, isolez-la dans le plus petit bloc possible.
- Préférez
cloneou l'emprunt pour la propagation des données.
Cela réduit les risques de conditions de concurrence et simplifie la concurrence.
Code déclaratif vs impératif
Le code impératif décrit les étapes d'exécution : boucles, conditions, mutations. Le code fonctionnel décrit le résultat souhaité. Pour obtenir les carrés des nombres de 0 à 9, la version impérative nécessite un vecteur et push :
let mut carres = Vec::new();
for num in 0..10 {
carres.push(num * num);
}
L'approche déclarative utilise une chaîne d'itérateurs :
let carres: Vec<i32> = (0..10)
.map(|x| x * x)
.collect();
Avantages du style déclaratif :
- Moins de code : les chaînes d'itérateurs remplacent les boucles.
- Immutabilité : pas de mutations de collection.
- Composabilité : les fonctions se combinent facilement.
- Concurrence :
.par_iter()permet le multithreading sans verrous.
Les itérateurs en Rust sont paresseux : map ne s'exécute pas avant collect ou for_each, optimisant la mémoire.
Gestion explicite des erreurs
Les exceptions dans d'autres langages masquent les erreurs : try-catch permet d'ignorer les types et le contexte. En Rust, Result<T, E> et ? nécessitent une gestion ou une propagation explicite.
Mauvais exemple avec unwrap :
vieux_grille_pain().unwrap(); // Panique en cas d'erreur
Approche correcte utilisant match ou map_err :
let toast = match vieux_grille_pain() {
Ok(t) => t,
Err(e) => {
eprintln!("Erreur : {}", e);
std::process::exit(1);
}
};
Conseils pour la gestion :
matchpour un contrôle total.map_errpour la transformation d'erreur.?seulement si l'appelant la gère.- Bibliothèques comme
anyhowouthiserrorpour plus de commodité.
Cela empêche les paniques en production et force la considération des erreurs au moment de la compilation.
Éliminer le vide implicite
Dans les langages impératifs, null rôde partout, provoquant NullPointerException. Rust utilise Option<T> : Some(valeur) ou None explicitement.
let peut_etre_bonbons: Option<Vec<Bonbon>> = obtenir_bonbons();
let bonbons = match peut_etre_bonbons {
Some(c) => c,
None => Vec::new(),
};
Le filtrage par motif garantit que les deux cas sont traités. Le compilateur ne négligera pas None.
Points clés à retenir
- Immutabilité par défaut réduit les bogues dus aux changements d'état.
- Itérateurs et map remplacent les boucles, rendant le code déclaratif.
- Result et Option imposent une gestion explicite des erreurs et du vide.
- Composition de fonctions simplifie les tests et le refactoring.
- Le style fonctionnel convient à 80% des tâches en Rust ; les mutations sont réservées aux E/S ou aux points chauds de performance.
— Editorial Team
Aucun commentaire pour le moment.