Rust

Any donation is very welcome
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 !");
}

Vous aurez noté que je n'ai pas mis de parenthèses (( et )) autour des conditions : elles sont superflues en Rust. Cependant, elles sont toujours 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 !

En bref : pas de parenthèses autour de la condition mais accolades obligatoires autour du bloc de la condition.

Je vais profiter de ce chapitre pour aborder le pattern matching.

Pattern matching

Définition wikipédia :

"Le filtrage par motif, en anglais pattern matching, 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 à ça, on peut comparer ce que l'on appelle des expressions de manière plus intuitive. Ceux ayant déjà utilisé des langages fonctionnels ne devraient pas se sentir dépaysés. Comme un code vaut souvent mieux que de longues explications :

Runlet my_string = "hello";

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

Ici ça affichera donc "anglais".

Comme vous vous en doutez, on peut s'en servir sur n'importe quel type de variable. Après tout, il sert à comparer des expressions, vous pouvez très bien matcher sur un i32 ou un f64 si vous en avez besoin.

Concernant le _, il signifie "toutes les autres expressions". C'est en quelque sorte le else du pattern matching (il fonctionne de la même manière que le default d'un switch C/C++/Java). Cependant, il est obligatoire de le mettre si toutes les expressions possibles n'ont pas été testées ! Dans le cas présent, il est impossible de tester toutes les strings existantes, on met donc _ à la fin. Si on teste un booléen, on pourra faire :

Runlet b = true;

match b {
    true => {
        // faire quelque chose
    }
    false => {
        // faire autre chose
    }
}

Car il n'y a que deux valeurs possibles et qu'elles ont toutes les deux été testées ! Un autre exemple en utilisant un i32 :

Runlet age: i32 = 18;

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

C'est le moment où vous vous dites quelque chose du genre "mais c'est nul ! On va pas s'amuser à écrire toutes les valeurs en dessous de 18 pour voir s'il est majeur !". Sachez que vous avez tout à fait raison. Dans le cas ci-dessus, le mieux serait d'écrire :

Runlet age: i32 = 17;

match age {
    tmp if tmp > 17 => {
        println!("majeur !");
    }
    _ => {
        println!("mineur !");
    }
}

Et là, vous vous demandez sans doute : "mais d'où il sort ce tmp ?!". C'est une variable créée (temporairement) à l'intérieur du bloc du match contenant la valeur de l'expression évaluée (la variable age en l'occurence, donc 17). Elle nous permet d'ajouter une condition à notre pattern matching afin d'affiner les résultats ! On peut donc ajouter des && ou des || selon les besoins.

Un petit détail avant de passer à la suite :

Runlet my_string = "hello";

let s = match my_string {
    "bonjour" => "francais",
    "ciao" => "italien",
    "hello" => "anglais",
    "hola" => "espagnol",
    _ => "je ne connais pas cette langue..."
}; // on met un ';' ici car ce match retourne un type

println!("{}", s);

Tout comme pour les if/else if/else, il est possible de retourner une valeur d'un pattern matching et donc de la mettre directement dans une variable. Du coup, le ';' est nécessaire pour terminer ce bloc. Essayez le code suivant si vous avez encore un peu de mal à comprendre :

Runfn main() {
    if 1 == 2 {
        "2"
    } else {
        "1"
    }
    println!("fini");
}

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)
};

À noter, dans le cas d’un for i in 10..100 { println!("{}", i); }, i prendra une valeur allant de 10 à 99 inclus.

Pratique ! 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 9 et 101", x),
    x => println!("{} n'est pas entre 9 et 101", x)
};

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

Runmatch une_variable {
    "jambon" | "poisson" | "oeuf" => 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". Dans le premier cas, si une_variable vaut "jambon", "poisson" ou "oeuf", le match rentrera dans cette condition, et ainsi de suite.

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