Énumérations Rust

Les énumérations en Rust ne sont pas aussi simples que le concept dans d'autres langages de programmation, mais elles peuvent encore être utilisées très simplement :

Exemple

#[derive(Debug)]

enum Book {
    Papery, Electronic
}

fn main() {
    let book = Book::Papery;
    println!("{:?}", book);
}

Sortie :

Papery

Les livres sont divisés en livres papier (Papery book) et en livres électroniques (Electronic book).

Si vous développez maintenant un système de gestion de bibliothèque et que vous devez décrire les différents attributs des deux types de livres (les livres papier ont une cote, les livres électroniques n'ont qu'une URL), vous pouvez ajouter des descriptions d'attributs de tuple aux membres de l'énumération :

enum Book {
    Papery(u32),
    Electronic(String),
}

let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));

Si vous souhaitez nommer les attributs, vous pouvez utiliser la syntaxe struct :

enum Book {
    Papery { index: u32 },
    Electronic { url: String },
}
let book = Book::Papery{index: 1001};

Bien que vous puissiez les nommer de cette façon, notez que vous ne pouvez pas accéder aux attributs liés à une variante d'enum comme vous accédez aux champs d'une struct. La façon d'y accéder est dans la syntaxe match.

Syntaxe match

Le but des enums est de classer un certain type de choses, et le but de la classification est de décrire différentes situations. Sur la base de ce principe, les types enum sont souvent finalement traités par des structures de branchement (switch dans de nombreux langages). La syntaxe switch est classique, mais elle n'est pas prise en charge dans Rust. La raison pour laquelle de nombreux langages abandonnent switch est que switch est sujet aux problèmes de fall-through causés par l'oubli d'ajouter break. Des langages comme Java et C# évitent cette situation grâce à des contrôles de sécurité.

Rust implémente les structures de branchement via l'instruction match. Voyons d'abord comment utiliser match pour gérer les enums :

Exemple

fn main() {
    enum Book {
        Papery {index: u32},
        Electronic {url: String},
    }
   
    let book = Book::Papery{index: 1001};
    let ebook = Book::Electronic{url: String::from("url...")};
   
    match book {
        Book::Papery { index } => {
            println!("Livre papier {}", index);
        },
        Book::Electronic { url } => {
            println!("Livre électronique {}", url);
        }
    }
}

Sortie :

Papery book 1001

Un bloc match peut aussi être traité comme une expression de fonction, et il peut avoir une valeur de retour :

match 枚举类实例 {
    分类1 => 返回值表达式,
    分类2 => 返回值表达式,
    ...
}

Mais les types de toutes les expressions de valeur de retour doivent être identiques !

Si les attributs supplémentaires d'une enum sont définis comme un tuple, vous devez temporairement spécifier un nom dans le bloc match :

Exemple

enum Book {
    Papery(u32),
    Electronic {url: String},
}
let book = Book::Papery(1001);

match book {
    Book::Papery(i) => {
        println!("{}", i);
    },
    Book::Electronic { url } => {
        println!("{}", url);
    }
}

En plus de faire des branchements sur des enums, match peut aussi faire des branchements sur des données de types tels que les entiers, les nombres à virgule flottante, les caractères et les références de tranches de chaînes (&str). Parmi ceux-ci, le branchement sur les nombres à virgule flottante est légal, mais il n'est pas recommandé car des problèmes de précision peuvent provoquer des erreurs de branchement.

Lorsque vous faites des branchements sur des types non-enum, vous devez gérer le cas par défaut, même s'il n'y a rien à faire dans ce cas..Le cas par défaut utilise un tiret bas_pour représenter :

Exemple

fn main() {
    let t = "abc";
    correspondance t {
        "abc" => println!("Yes"),
        _ => {},
    }
}

Énumération Option

Option est une énumération dans la bibliothèque standard Rust. Ce type est utilisé pour combler le fait que Rust ne prend pas en charge les références null.

De nombreux langages prennent en charge l'existence de null (C/C++, Java), ce qui est pratique, mais cela crée aussi d'énormes problèmes. L'inventeur de null l'a également admis, « une idée pratique a causé des pertes cumulées d'un milliard de dollars. »

null porte souvent un coup fatal au programme lorsque les développeurs traitent tout comme non null : après tout, dès qu'une telle erreur se produit, l'exécution du programme doit complètement s'arrêter.

Pour résoudre ce problème, de nombreux langages n'autorisent pas null par défaut, mais prennent en charge l'apparition de null au niveau du langage (souvent marqué par un symbole ? avant le type).

Java prend en charge null par défaut, mais peut restreindre l'apparition de null via l'annotation @NotNull. C'est une façon de faire face.

Rust interdit complètement les valeurs null au niveau du langage, mais malheureusement null peut efficacement résoudre un petit nombre de problèmes, c'est pourquoi Rust a introduit l'énumération Option :

enum Option<T> {
    Some(T),
    None,
}

Si vous voulez définir un type qui peut être null, vous pouvez faire ceci :

let opt = Option::Some("Hello");

Si vous voulez effectuer certaines opérations sur opt, vous devez d'abord déterminer s'il estOption::None:

Exemple

fn main() {
    let opt = Option::Some("Hello");
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}

Sortie :

Hello

Si votre variable est initialement null, pensez au compilateur : comment sait-il de quel type est la variable lorsque la valeur n'est pas null ?

Par conséquent, une Option avec une valeur initiale null doit avoir un type explicite :

Exemple

fn main() {
    let opt: Option<&str> = Option::None;
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}

Sortie :

opt is nothing

Cette conception rend la programmation avec des valeurs null plus difficile, mais c'est exactement ce qu'il faut pour construire un système stable et efficace. Comme Option est introduit par défaut dans le compilateur Rust, vous pouvez omettre Option:: et écrire directement None ou Some() lorsque vous l'utilisez.

Option est un type d'énumération spécial qui peut contenir des valeurs pour la sélection de branche :

Exemple

fn main() {
        let t = Some(64);
        correspondance t {
                Some(64) => println!("Yes"),
                _ => println!("No"),
        }
}

Syntaxe de if let

Exemple

let i = 0;
correspondance i {
    0 => println!("zero"),
    _ => {},
}

Placez-le dans la fonction main, la sortie est :

zero

Le but de ce programme est de déterminer si i est le nombre 0, et si c'est le cas, d'afficher zero.

Maintenant, utilisez la syntaxe if let pour raccourcir ce code :

let i = 0;
if let 0 = i {
    println!("zero");
}

La syntaxe de if let est la suivante :

if let 匹配值 = 源变量 {
    语句块
}

Vous pouvez ensuite ajouter un bloc else pour gérer le cas fourre-tout.

La syntaxe if let peut être considérée comme du "sucre syntaxique" pour une instruction match qui ne distingue que deux cas (le sucre syntaxique désigne une alternative pratique avec le même principe sous-jacent qu'une certaine syntaxe).

Cela s'applique également aux énumérations :

Exemple

fn main() {
    enum Book {
        Papery(u32),
        Electronic(String)
    }
    let book = Book::Electronic(String::from("url"));
    if let Book::Papery(index) = book {
        println!("Papery {}", index);
    } else {
        println!("Not papery book");
    }
}
Autres extensions