Acheter version papier

Rust

Fork me on GitHub

I. Les bases de la programmation en Rust

5. Conditions et pattern matching

Nous allons d'abord commencer par les conditions :

if / else if / else

Les if / else if / else fonctionnent de la même façon qu'en C/C++/Java :

Runlet age: i32 = 17;

if age >= 18 {
    println!("majeur !");
} else {
    println!("mineur !");
}

Notez que je n'ai pas mis de parenthèses, ( et ), autour des conditions : elles sont superflues en Rust. Cependant, elles seront nécessaires si vous faites des "sous"-conditions :

Runif age > 18 && (age == 20 || age == 24) {
    println!("ok");
}

Par contre, les accolades { et } sont obligatoires, même si le bloc de votre condition ne contient qu'une seule ligne de code !

Pour rappel : && est la condition et tandis que || est la condition ou. Donc dans le code au-dessus, il faut le lire comme ceci :

age doit être supérieur à 18 ET age doit être égal à 20 OU 24.

Comparaison de booléens

Si vous souhaitez faire une comparaison avec un booléen (donc true ou false), sachez qu'il est possible de les écrire de façon plus courte :

Runif x == true {}
// est équivalent à:
if x {}

if x == false {}
// est équivalent à:
if !x {}

Pattern matching

Rappelons dans un premier temps la définition du pattern matching ou "filtrage par motif" : c'est la vérification de la présence de constituants d'un motif par un programme informatique ou parfois par un matériel spécialisé.

Pour dire les choses plus simplement, c'est une condition permettant de faire les choses de manière différente. Grâce à lui, on peut comparer ce que l'on appelle des expressions de manière plus intuitive (nous aborderons les expressions un peu plus loin dans ce cours). Si vous avez déjà utilisé des langages fonctionnels, vous ne vous sentirez pas dépaysés.

Regardons un exemple pour faciliter les explications :

Runlet my_string = "hello";

match my_string {
    "bonjour" => println!("français"),
    "ciao" => println!("italien"),
    "hello" => println!("anglais"),
    "hola" => println!("espagnol"),
    _ => println!("je ne connais pas cette langue..."),
}

Ici ça affichera "anglais".

On peut s'en servir sur n'importe quel type. Après tout, il sert à comparer avec des patterns (motifs en français). Vous pouvez très bien matcher sur un i32 ou sur un f64 si vous voulez.

Concernant le _ utilisé dans la dernière branche du match, il s'agit d'une variable (nommée ainsi pour éviter un warning pour variable non utilisée, _a aurait eu le même résultat) qui contient "tous les autres patterns". C'est en quelque sorte le else du pattern matching. Il est obligatoire de l'utiliser si tous les patterns possibles n'ont pas été testés ! Dans le cas présent, il est impossible de tester toutes les strings existantes, on utilise donc _ à la fin. Si on teste un booléen, il n'y aura que deux valeurs possibles et il sera possible de les tester toutes les deux :

Runlet b = true;

match b {
    true => println!("true"),
    false => println!("false"),
}

La syntaxe du pattern matching suit ce modèle :

pattern => expression,

Le => sert à séparer le pattern de l'expression. La virgule sert à marquer la fin de l'expression.

Si vous souhaitez faire plus d'une chose dans une branche du pattern matching, on peut ajouter des accolades :

Runlet b = true;
let mut x = 0;

match b {
    true => {
        println!("true");
        x += 1;
    }
    false => println!("false"),
}

Si vous utilisez des accolades, la virgule devient optionnelle (c'est pourquoi elle n'est en pratique jamais présente dans ce cas).

Un autre exemple en utilisant un i32 :

Runlet age: i32 = 18;

match age {
    17 => {
        println!("mineur !");
    }
    18 => {
        println!("majeur !");
    }
    x => {
        println!("ni 17, ni 18 mais {} !", x);
    }
}

Il est bien évidemment possible de matcher plus d'une valeur à la fois, pas besoin d'écrire toutes les valeurs en dessous de 18 pour voir s'il est mineur. Si on reprend le précédent exemple, le mieux serait d'écrire :

Runlet age: i32 = 17;

match age {
    tmp if tmp > 60 => {
        println!("plus tout jeune...");
    }
    tmp if tmp > 17 => {
        println!("majeur !");
    }
    _ => {
        println!("mineur !");
    }
}

Comme vous l'avez sans doute compris, il est possible d'ajouter une condition à un pattern matching. Elle nous permet d'affiner les résultats ! On peut donc ajouter des && ou des || selon les besoins.

Toujours plus loin !

Il est aussi possible de matcher directement sur un ensemble de valeurs de cette façon :

Runlet i = 0i32;

match i {
    10..=100 => println!("La variable est entre 10 et 100 (inclus)"),
    x => println!("{} n'est pas entre 10 et 100 (inclus)", x),
}

Vous pouvez aussi "binder" (ou matcher sur un ensemble de valeurs) la variable avec le symbole "@" :

Runlet i = 0i32;

match i {
    x @ 10..=100 => println!("{} est entre 10 et 100 (inclus)", x),
    x => println!("{} n'est pas entre 10 et 100 (inclus)", x),
}

Cela permet de stocker la valeur sur laquelle on matche directement dans x, ce qui est pratique si vous avez besoin de cette valeur (pour l'afficher comme dans le code au-dessus par-exemple).

Il ne nous reste maintenant plus qu'un dernier point à aborder :

Runmatch une_variable {
    "jambon" | "poisson" | "œuf" => println!("Des protéines !"),
    "bonbon" => println!("Des bonbons !"),
    "salade" | "épinards" | "fenouil" => println!("Beurk ! Des légumes !"),
    _ => println!("ça, je sais pas ce que c'est..."),
}

Vous l'aurez sans doute deviné : ici, le '|' sert de condition "ou" sur le pattern. Dans le premier cas, si une_variable vaut "jambon", "poisson" ou "œuf", le match rentrera dans cette condition, et ainsi de suite.

Voilà qui clôt ce chapitre sur les conditions et le pattern matching. N'hésitez pas à revenir sur les points que vous n'êtes pas sûr d'avoir parfaitement compris, car il s'agit vraiment de la base de ce langage. Si quelque chose n'est pas parfaitement maîtrisé, vous risquez d'avoir du mal à comprendre la suite.