Les enums en Rust
Un enum (énumération) définit un type qui peut prendre une valeur parmi un ensemble fixe de possibilités. Si une struct sert à dire « cette chose a tous ces champs », un enum sert à dire « cette chose est exactement une de ces options ».
Définir et utiliser un enum
Section intitulée « Définir et utiliser un enum »enum Direction { North, South, East, West,}
fn main() { let heading = Direction::North;
match heading { Direction::North => println!("Cap au nord"), Direction::South => println!("Cap au sud"), Direction::East => println!("Cap à l'est"), Direction::West => println!("Cap à l'ouest"), }}Sortie :
Cap au nordRemarquez que ce match n’a pas besoin d’un cas fourre-tout _ : toutes les Direction possibles sont listées, donc Rust sait déjà que le match est complet.
Les enums peuvent contenir des données
Section intitulée « Les enums peuvent contenir des données »C’est là que les enums de Rust vont plus loin que les enums classiques des autres langages : chaque variante peut transporter ses propres données.
enum Shape { Circle(f64), // rayon Rectangle(f64, f64), // largeur, hauteur}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 3.14159 * radius * radius, Shape::Rectangle(width, height) => width * height, }}
fn main() { let circle = Shape::Circle(3.0); let rectangle = Shape::Rectangle(4.0, 5.0);
println!("{:.2}", area(&circle)); println!("{:.2}", area(&rectangle));}Sortie :
28.2720.00Shape::Circle transporte un f64 (le rayon) ; Shape::Rectangle en transporte deux. Dans chaque branche, match extrait directement ces données pour vous : pas besoin d’une étape séparée pour les « déballer ».
Les enums peuvent aussi avoir des méthodes
Section intitulée « Les enums peuvent aussi avoir des méthodes »Tout comme une struct, un enum peut avoir un bloc impl avec des méthodes. Dans une méthode, on fait généralement un match self pour décider quoi faire selon la variante :
#[derive(Debug)]enum Light { Red, Yellow, Green,}
impl Light { fn seconds(&self) -> u32 { match self { Light::Red => 30, Light::Yellow => 3, Light::Green => 25, } }
fn next(&self) -> Light { match self { Light::Red => Light::Green, Light::Green => Light::Yellow, Light::Yellow => Light::Red, } }}
fn main() { let mut light = Light::Red;
for _ in 0..4 { println!("{:?} pendant {} secondes", light, light.seconds()); light = light.next(); }}Sortie :
Red pendant 30 secondesGreen pendant 25 secondesYellow pendant 3 secondesRed pendant 30 secondesnext renvoie un tout nouveau Light, et light = light.next(); remplace l’ancien. C’est pour ça que light est déclaré mut. #[derive(Debug)] fonctionne sur les enums exactement comme sur les structs : {:?} affiche le nom de la variante (ici Red, Green, Yellow, puisque les noms dans le code restent en anglais).
Mettre la logique dans des méthodes la garde à côté du type. Quiconque utilise un Light peut appeler light.seconds() sans savoir comment la durée est choisie. Et si vous ajoutez plus tard une nouvelle variante, comme FlashingYellow (orange clignotant), le compilateur vous montre chaque match à mettre à jour.
Option : un enum que vous utilisez déjà
Section intitulée « Option : un enum que vous utilisez déjà »Le type Option, que vous découvrirez en détail sur sa propre page, n’est qu’un enum défini dans la bibliothèque standard :
enum Option<T> { Some(T), None,}Une fois que vous avez compris les enums, Option ne ressemble plus à une syntaxe spéciale : c’est un enum tout à fait ordinaire à deux variantes, dont l’une transporte une valeur.
Pourquoi pas une struct avec un champ « type » ?
Section intitulée « Pourquoi pas une struct avec un champ « type » ? »On pourrait imaginer simuler cela avec une struct et un champ texte comme "circle" ou "rectangle". Mais alors rien ne vous empêche de taper "circl" par erreur, et il faudrait vérifier à la main quels champs ont vraiment un sens pour chaque type. Un enum rend les combinaisons invalides impossibles à écrire dès le départ, et le compilateur vous oblige à traiter chaque variante.
Exercices
Section intitulée « Exercices »1. Compter les pièces
Section intitulée « 1. Compter les pièces »Créez un enum Coin avec les variantes Penny, Nickel, Dime et Quarter (les pièces américaines de 1, 5, 10 et 25 cents). Écrivez fn value_in_cents(coin: &Coin) -> u32, puis additionnez le contenu d’un porte-monnaie : un quarter, un dime, un nickel, un penny et encore un quarter. Indice : rangez les pièces dans un tableau et parcourez &purse avec une boucle.
Voir la solution
enum Coin { Penny, Nickel, Dime, Quarter,}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}
fn main() { let purse = [Coin::Quarter, Coin::Dime, Coin::Nickel, Coin::Penny, Coin::Quarter];
let mut total = 0; for coin in &purse { total += value_in_cents(coin); }
println!("Total : {total} cents");}Sortie :
Total : 66 cents2. Ajouter un triangle
Section intitulée « 2. Ajouter un triangle »Reprenez l’enum Shape de cette page et ajoutez une variante Triangle qui contient les longueurs de ses trois côtés. Écrivez ensuite une fonction perimeter (la longueur du tour complet de la forme) qui fonctionne pour toutes les formes. Indice : le périmètre d’un cercle vaut 2.0 * 3.14159 * radius. Celui d’un triangle, c’est la somme de ses trois côtés.
Voir la solution
enum Shape { Circle(f64), Rectangle(f64, f64), Triangle(f64, f64, f64),}
fn perimeter(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 2.0 * 3.14159 * radius, Shape::Rectangle(width, height) => 2.0 * (width + height), Shape::Triangle(a, b, c) => a + b + c, }}
fn main() { let shapes = [ Shape::Circle(1.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle(3.0, 4.0, 5.0), ];
for shape in &shapes { println!("{:.2}", perimeter(shape)); }}Sortie :
6.2818.0012.00Essayez de supprimer la branche Shape::Triangle du match. Rust refuse de compiler, car le match ne couvre plus toutes les variantes. C’est le compilateur qui s’assure que vous n’oubliez jamais un cas.
3. Les températures
Section intitulée « 3. Les températures »Créez un enum Temperature avec deux variantes qui contiennent chacune un f64 : Celsius et Fahrenheit. Ajoutez une méthode to_celsius(&self) -> f64, et affichez Celsius(25.0), Fahrenheit(212.0) et Fahrenheit(50.0) en °C avec une décimale. Indice : pour convertir, utilisez (f - 32.0) * 5.0 / 9.0.
Voir la solution
enum Temperature { Celsius(f64), Fahrenheit(f64),}
impl Temperature { fn to_celsius(&self) -> f64 { match self { Temperature::Celsius(c) => *c, Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0, } }}
fn main() { let readings = [ Temperature::Celsius(25.0), Temperature::Fahrenheit(212.0), Temperature::Fahrenheit(50.0), ];
for t in &readings { println!("{:.1} °C", t.to_celsius()); }}Sortie :
25.0 °C100.0 °C10.0 °CComme self est emprunté, c et f sont des références (&f64). Les calculs fonctionnent directement sur des références, mais pour renvoyer la valeur Celsius elle-même, vous écrivez *c, qui signifie « le nombre vers lequel pointe c ».
Récapitulatif
Section intitulée « Récapitulatif »- Un enum définit un type comme l’une de plusieurs variantes nommées :
enum Direction { North, South, ... }. - Les variantes peuvent transporter leurs propres données, et des variantes différentes peuvent transporter des données différentes.
matchdéstructure naturellement les données d’un enum et exige que chaque variante soit traitée.- Les enums peuvent avoir des méthodes dans un bloc
impl, tout comme les structs. Option(etResult, que vous verrez plus tard) ne sont que des enums de la bibliothèque standard : ils n’ont rien de magique.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Que peut faire chaque variante d'un enum Rust, contrairement à un enum classique des autres langages ?
2. Pourquoi l'exemple de match sur Direction n'a-t-il pas besoin d'une branche fourre-tout _ ?
3. Comment Option<T> est-il réellement implémenté dans la bibliothèque standard ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Defining an Enum — The Rust Book (en anglais) : les variantes avec données, les méthodes sur les enums, et pourquoi Rust a
Optionau lieu de null. - The
matchControl Flow Construct — The Rust Book (en anglais) : le match sur les enums en profondeur, avec un exemple de tri de pièces et les motifs fourre-tout. - Enums — Rust by Example (en anglais) : des exemples exécutables, dont des variantes avec des champs nommés comme
Click { x: i64, y: i64 }. - Enumerations — The Rust Reference (en anglais) : toutes les règles, par exemple comment donner des valeurs numériques explicites aux variantes.
Option— documentation de la bibliothèque standard (en anglais) : la vraie définition de l’enum que vous utiliserez le plus, et toutes ses méthodes utiles.