ProgrammationDéveloppeur Rust intermédiaire

En quoi consiste le principe de l'utilisation la plus efficace des enums en Rust pour la modélisation de l'état et des erreurs de manière typée, et quelles subtilités du pattern matching faut-il prendre en compte ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les enums en Rust diffèrent radicalement des enums en C/C++ : ils peuvent stocker des données associées et sont idéaux pour la modélisation de l'état et des erreurs. Ils permettent de construire des machines à états finis typées, différents types d'Option/Result, et d'implémenter le pattern "sum types". Historiquement, des constructions analogues étaient utilisées dans les langages de programmation fonctionnels pour décrire les variantes d'une entité avec des options strictement séparées.

Problème : obtenir de l'expressivité (exprimer tous les états possibles), où chaque cas de traitement est obligatoire, et il est impossible de négliger une branche accidentellement. Les erreurs à l'échelle du projet sont difficiles à typer sans une telle structure expressive.

Solution : les enums avec des données associées et le pattern matching offrent du contrôle — chaque branche est vérifiée par le compilateur, l'exhaustivité est garantie. De plus, pour Result et Option, de nombreux outils auxiliaires sont déjà disponibles.

Exemple de code :

enum NetworkState { Disconnected, Connecting(u32), // numéro de tentative Connected(String), Error(String), } fn print_state(state: NetworkState) { match state { NetworkState::Disconnected => println!("Net : déconnecté"), NetworkState::Connecting(count) => println!("Net : connexion (tentative {})", count), NetworkState::Connected(addr) => println!("Net : connecté à {}", addr), NetworkState::Error(msg) => println!("Erreur réseau : {}", msg), } }

Caractéristiques clés :

  • Un enum peut avoir différentes variantes avec leur propre type de données
  • Le pattern matching garantit le traitement de toutes les variantes (ou avertit des omissions)
  • Permet d'exprimer les erreurs sans exceptions, avec sécurité typée

Questions pièges.

Est-il possible de traiter partiellement les branches d'un enum sans _ ?

Le compilateur interdit les cas non couverts pour les enums non exhaustifs, mais si vous utilisez _, les branches non traitées seront "absorbées". Il convient d'éviter _ pour les branches critiques afin que des modifications futures ne passent pas inaperçues.

Dans quels cas les valeurs associées sont-elles référencées, et dans quels cas sont-elles copiées lors du pattern matching ?

Lors du pattern matching, les données associées sont, par défaut, déplacées (move). Si vous souhaitez uniquement consulter, utilisez des références :

match &state { NetworkState::Connected(addr) => println!("par référence : {}", addr), _ => {} }

Peut-on utiliser deux enums avec des variantes de noms qui se chevauchent dans une même structure ?

Oui, mais les noms des variantes sont utilisés avec le préfixe de l'enum. Cela empêche les collisions et rend le code auto-documenté (par exemple, Status::Ok vs NetworkState::Ok).

Erreurs typiques et anti-patterns

  • Utilisation de _ pour masquer de nouvelles variantes lors de l'extension d'un enum
  • Déplacement de données significatives de l'enum lors du pattern matching par inadvertance
  • Abus du catch-all (par exemple, _ =>) dans des gestionnaires d'erreurs critiques

Exemple de la vie réelle

Cas négatif

Dans le code, le traitement de Result<T, E> a toujours un catch-all via _ =>, et de nouvelles erreurs (lors de l'extension de l'enum) passent inaperçues — des pertes d'erreurs silencieuses se produisent.

Avantages :

  • Concision du code, minimum de boilerplate

Inconvénients :

  • Perte de contrôle sur le flux d'exécution, des échecs fatals restent non pris en compte

Cas positif

On utilise le matching d'exhaustivité, chaque variante de l'enum est traitée explicitement, soit un panic pour une branche sans comportement connu.

Avantages :

  • Clarté de la logique et maintenabilité transparente
  • Lors de l'ajout d'un nouvel état, le compilateur avertit à temps

Inconvénients :

  • Parfois, le code est plus long, nécessite de traiter explicitement toutes les variantes