Retour à l'accueil

Rust fonctionnel : immutabilité et itérateurs

L'article explique les avantages du style fonctionnel en Rust : immutabilité au lieu des mutations, itérateurs déclaratifs au lieu des boucles, gestion explicite des erreurs avec Result et Option. Des exemples de code et des recommandations pour les développeurs intermédiaires/seniors sont fournis.

Code propre en Rust : approche fonctionnelle sans mutations
Advertisement 728x90

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.

Google AdInline article slot
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 clone ou l'emprunt pour la propagation des données.

Cela réduit les risques de conditions de concurrence et simplifie la concurrence.

Google AdInline article slot

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 :

Google AdInline article slot
  • 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 :

  • match pour un contrôle total.
  • map_err pour la transformation d'erreur.
  • ? seulement si l'appelant la gère.
  • Bibliothèques comme anyhow ou thiserror pour 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

Advertisement 728x90

Lire ensuite