#10 Enums et pattern matching
enum, match, if let
Définir des enums
Un enum (énumération) permet de définir un type qui peut être l'un de plusieurs variants. C'est parfait pour modéliser des données qui peuvent prendre différentes formes.
enum Direction {
Nord,
Sud,
Est,
Ouest,
}
let d = Direction::Nord;Chaque variant est un constructeur du type Direction. On y accède avec la syntaxe ::.
Enums avec données
Contrairement à d'autres langages, les enums de Rust peuvent contenir des données. Chaque variant peut avoir son propre type de données.
enum AdresseIp {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let locale = AdresseIp::V4(127, 0, 0, 1);
let loopback = AdresseIp::V6(String::from("::1"));
afficher_adresse(locale);
}
fn afficher_adresse(adresse: AdresseIp) {
match adresse {
AdresseIp::V4(a, b, c, d) => {
println!("Adresse IPv4 : {a}.{b}.{c}.{d}");
}
AdresseIp::V6(s) => {
println!("Adresse IPv6 : {s}");
}
}
}Chaque variant peut contenir différents types : des tuples, des structs anonymes, ou même rien du tout. C'est beaucoup plus expressif qu'un simple enum d'entiers.
enum Message {
Quitter, // Pas de données
Deplacer { x: i32, y: i32 }, // Struct anonyme
Ecrire(String), // Un String
ChangerCouleur(u8, u8, u8), // Un tuple
}L'expression match
match est l'outil principal pour travailler avec les enums. Le compilateur vous oblige à gérer tous les variants — impossible d'en oublier un.
enum Forme {
Cercle(f64),
Rectangle(f64, f64),
Triangle(f64, f64),
}
fn aire(forme: Forme) -> f64 {
match forme {
Forme::Cercle(rayon) => {
std::f64::consts::PI * rayon * rayon
}
Forme::Rectangle(l, h) => l * h,
Forme::Triangle(base, hauteur) => base * hauteur / 2.0,
}
}Le match est une expression : il retourne une valeur. Chaque branche utilise => pour séparer le motif de l'action. L'opérateur _ capture tous les cas restants.
La syntaxe if let
Quand vous ne vous intéressez qu'à un seul variant, if let est plus concis qu'un match complet :
let valeur: Option<i32> = Some(42);
// Avec match (verbeux)
match valeur {
Some(n) => println!("Le nombre est : {n}"),
None => (),
}
// Avec if let (concis)
if let Some(n) = valeur {
println!("Le nombre est : {n}");
}if let est du sucre syntaxique pour un match qui ne traite qu'un seul cas. Vous pouvez ajouter un else pour gérer les autres cas.
Enums courants (Option, Result)
Deux enums sont omniprésents en Rust et inclus dans le prélude (disponibles sans import) :
// Défini dans la bibliothèque standard :
// enum Option<T> {
// Some(T),
// None,
// }
fn diviser(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None // Division par zéro impossible
} else {
Some(a / b)
}
}
fn main() {
match diviser(10.0, 3.17) {
Some(resultat) => println!("Résultat : {resultat:.2}"),
None => println!("Division par zéro !"),
}
}Option remplace les valeurs null des autres langages. En Rust, si une valeur peut être absente, elle est de type Option<T> — le compilateur vous oblige à gérer le cas None.
// Défini dans la bibliothèque standard :
// enum Result<T, E> {
// Ok(T),
// Err(E),
// }
use std::fs;
fn main() {
let contenu = fs::read_to_string("fichier.txt");
match contenu {
Ok(texte) => println!("Lecture du fichier réussie : {texte}"),
Err(e) => println!("Erreur de lecture : {e}"),
}
}Result est utilisé pour les opérations qui peuvent échouer. Ok(T) contient la valeur en cas de succès, Err(E) contient l'erreur. C'est la base de la gestion d'erreurs en Rust — pas d'exceptions, juste des types.
À vous de jouer
Essayez les commandes ci-dessous pour voir les enums en action :