Programmer le jeu du plus ou du moins
Entrons dans le vif du sujet en travaillant ensemble sur un projet concret !
Ce chapitre présente quelques concepts couramment utilisés en Rust en vous
montrant comment les utiliser dans un véritable programme. Nous aborderons
notamment les instructions let
et match
, les méthodes et fonctions
associées, l'utilisation des crates, et bien plus encore ! Dans les chapitres
suivants, nous approfondirons ces notions. Dans ce chapitre, vous n'allez
exercer que les principes de base.
Nous allons coder un programme fréquemment réalisé par les débutants en programmation : le jeu du plus ou du moins. Le principe de ce jeu est le suivant : le programme va tirer au sort un nombre entre 1 et 100. Il invitera ensuite le joueur à saisir un nombre qu'il pense deviner. Après la saisie, le programme indiquera si le nombre saisi par le joueur est trop grand ou trop petit. Si le nombre saisi est le bon, le jeu affichera un message de félicitations et se fermera.
Mise en place d'un nouveau projet
Pour créer un nouveau projet, rendez-vous dans le dossier projects que vous avez créé au chapitre 1 et utilisez Cargo pour créer votre projet, comme ceci :
$ cargo new jeu_du_plus_ou_du_moins
$ cd jeu_du_plus_ou_du_moins
La première commande, cargo new
, prend comme premier argument le nom de notre
projet (jeu_du_plus_ou_du_moins
). La seconde commande nous déplace dans le
dossier de notre nouveau projet créé par Cargo.
Regardons le fichier Cargo.toml qui a été généré :
Fichier : Cargo.toml
[package]
name = "jeu_du_plus_ou_du_moins"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Comme vous l'avez expérimenté dans le chapitre 1, cargo new
génère un
programme “Hello, world!” pour vous. Ouvrez le fichier src/main.rs :
Fichier : src/main.rs
fn main() { println!("Hello, world!"); }
Maintenant, lançons la compilation de ce programme “Hello, world!” et
son exécution en une seule commande avec cargo run
:
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
Running `target/debug/jeu_du_plus_ou_du_moins`
Hello, world!
Cette commande run
est très pratique lorsqu'on souhaite itérer rapidement
sur un projet, comme c'est le cas ici, pour tester rapidement chaque
modification avant de passer à la suivante.
Ouvrez à nouveau le fichier src/main.rs. C'est dans ce fichier que nous écrirons la totalité de notre code.
Traitement d'un nombre saisi
La première partie du programme consiste à demander au joueur de saisir du texte, à traiter cette saisie, et à vérifier que la saisie correspond au format attendu. Commençons par permettre au joueur de saisir son nombre. Entrez le code de l'encart 2-1 dans le fichier src/main.rs.
Fichier : src/main.rs
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Ce code contient beaucoup d'informations, nous allons donc l'analyser petit
à petit. Pour obtenir la saisie utilisateur et ensuite l'afficher, nous avons
besoin d'importer la bibliothèque d'entrée/sortie io
(initiales
de input/output) afin de pouvoir l'utiliser. La bibliothèque io
provient de
la bibliothèque standard, connue sous le nom de std
:
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Par défaut, Rust importe dans la portée de tous les programmes quelques fonctionnalités définies dans la bibliothèque standard. Cela s'appelle l'étape préliminaire (the prelude), et vous pouvez en savoir plus dans sa documentation de la bibliothèque standard.
Si vous
voulez utiliser un type qui ne s'y trouve pas, vous devrez l'importer
explicitement avec l'instruction use
. L'utilisation de la bibliothèque
std::io
vous apporte de nombreuses fonctionnalités utiles, comme ici la
possibilité de récupérer une saisie utilisateur.
Comme vous l'avez vu au chapitre 1, la fonction main
est le point d'entrée
du programme :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Le mot clé fn
déclare une nouvelle fonction, les parenthèses ()
indiquent
que cette fonction n'accepte aucun paramètre, et l'accolade ouvrante {
marque
le début du corps de la fonction.
Comme vous l'avez également appris au chapitre 1, println!
est une macro qui
affiche une chaîne de caractères à l'écran :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Ce code affiche du texte qui indique le titre de notre jeu, et un autre qui demande au joueur d'entrer un nombre.
Enregistrer des données dans des variables
Ensuite, on crée une variable pour stocker la saisie de l'utilisateur, comme ceci :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Le programme commence à devenir intéressant ! Il se passe beaucoup de choses
dans cette petite ligne. Nous utilisons l'instruction let
pour créer la
variable. Voici un autre exemple :
let pommes = 5;
Cette ligne permet de créer une nouvelle variable nommée pommmes
et à lui
assigner la valeur 5. Par défaut en Rust, les variables sont immuables.
Nous aborderons plus en détail cette notion dans la section “Variables et
Mutabilité” au chapitre 3. Pour
rendre une variable mutable (c'est-à-dire modifiable), nous ajoutons mut
devant le nom de la variable :
#![allow(unused)] fn main() { let pommes = 5; // immuable let mut bananes = 5; // mutable, modifiable }
Remarque : La syntaxe
//
permet de commencer un commentaire qui s'étend jusqu'à la fin de la ligne. Rust ignore tout ce qu'il y a dans un commentaire. Nous verrons plus en détail les commentaires dans le chapitre 3.
Lorsque vous revenez sur le jeu du plus ou du moins, vous comprenez donc
maintenant que la ligne let mut supposition
permet de créer une variable
mutable nommée supposition
. Le signe égal (=
) indique à Rust que nous
voulons désormais lier quelquechose à la variable. A la droite du signe égal,
nous avons la valeur liée à supposition
, qui est ici le résultat de
l'utilisation de String::new
, qui est une fonction qui retourne une nouvelle
instance de String
.
String
est un type de chaîne de caractères fourni
par la bibliothèque standard, qui est une portion de texte encodée en UTF-8 et
dont la longueur peut augmenter.
La syntaxe ::
dans String::new()
indique que new
est une fonction
associée au type String
. Une fonction associée est une fonction qui est
implémentée sur un type, ici String
. Cette fonction new
crée une nouvelle
chaîne de caractères vide, une nouvelle String
. Vous trouverez fréquemment
une fonction new
sur d'autres types, car c'est un nom souvent donné à une
fonction qui crée une nouvelle valeur ou instance d'un type.
En définitif, la ligne let mut supposition = String::new();
crée une nouvelle
variable mutable qui contient une nouvelle chaîne de caractères vide, une
instance de String
. Ouf !
Recueillir la saisie utilisateur
Rappelez-vous que nous avons importé les fonctionnalités d'entrée/sortie de la
bibliothèque standard avec use std::io;
à la première ligne de notre
programme. Nous allons maintenant appeler la fonction stdin
du module io
,
qui va nous permettre de traiter la saisie utilisateur :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Si nous n'avions pas importé la bibliothèque io
avec use std::io
au début
du programme, on aurait toujours pu utiliser la fonction en écrivant l'appel à
la fonction de cette manière : std::io::stdin
. La fonction stdin
retourne
une instance de std::io::Stdin
, qui est un type qui
représente une référence abstraite (handle) vers l'entrée standard du
terminal dans lequel vous avez lancé le programme.
Ensuite, la ligne .read_line(&mut supposition)
appelle la méthode
read_line
sur l'entrée standard afin d'obtenir
la saisie utilisateur.
Nous passons aussi &mut supposition
en argument de read_line
pour lui
indiquer dans quelle chaîne de caractère il faut stocker la saisie utilisateur.
Le but final de read_line
est de récupérer tout ce que l'utilisateur écrit
dans l'entrée standard et de l'ajouter à la fin d'une chaîne de caractères
(sans écraser son contenu) ; c'est pourquoi nous passons cette chaîne de
caractères en argument. Cet argument doit être mutable pour que read_line
puisse en modifier le contenu.
Le &
indique que cet argument est une référence, ce qui permet de laisser
plusieurs morceaux de votre code accéder à une même donnée sans avoir besoin
de copier ces données dans la mémoire plusieurs fois. Les références sont une
fonctionnalité complexe, et un des avantages majeurs de Rust est qu'il rend sûr
et simple l'utilisation des références. Il n'est pas nécessaire de trop
s'apesantir sur les références pour terminer ce programme.
Pour l'instant, tout ce que vous devez savoir est que comme les variables, les
références sont immuables par défaut.
D'où la nécessité d'écrire &mut supposition
au lieu de &supposition
pour la
rendre mutable. (Le chapitre 4 expliquera plus en détail les références.)
Gérer les erreurs potentielles avec le type Result
Nous avons encore du travail sur cette ligne de code. Même si nous allons rajouter une troisième ligne de code, elle ne fait partie que d'une seule ligne de code. Cette nouvelle partie rajoute cette méthode :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Nous aurions pu écrire ce code de cette manière :
io::stdin().read_line(&mut supposition).expect("Échec de la lecture de l'entrée utilisateur");
Cependant, une longue ligne de code n'est pas toujours facile à lire, c'est donc
une bonne pratique de la diviser. Il est parfois utile d'ajouter une nouvelle
ligne et des espaces afin de désagréger les longues lignes lorsque vous
appelerez une méthode, comme ici avec la syntaxe .nom_de_la_methode()
.
Maintenant, voyons à quoi sert cette ligne.
Comme expliqué précédemment, read_line
stocke dans la variable qu'on lui
passe en argument tout ce que l'utilisateur a saisi, mais cette fonction
retourne aussi une valeur − dans notre cas, de type
io::Result
. Il existe plusieurs types nommés
Result
dans la bibliothèque standard de Rust : un type générique
Result
ainsi que des déclinaisons spécifiques à
des sous-modules, comme io::Result
. Les types Result
sont des
énumérations, aussi appelées enums, qui peuvent
avoir un certain nombre de valeurs prédéfinies que l'on appelle variantes.
Les énumérations sont souvent utilisées avec match
, une structure
conditionelle qui facilite l'exécution d'un code différent en fonction de la
variante dans l'énumération au moment de son évaluation.
Le chapitre 6 explorera les énumérations plus en détail. La raison d'être du
type Result
est de coder des informations pour la gestion des erreurs.
Les variantes de Result
sont Ok
et Err
. La variante Ok
signifie que
l'opération a fonctionné, et à l'intérieur de Ok
se trouve la valeur générée
avec succès. La variante Err
signifie que l'opération a échoué, et Err
contient les informations décrivant comment ou pourquoi l'opération a échoué.
Les valeurs du type Result
, comme pour tous les types, ont des méthodes
qui leur sont associées. Par exemple, une instance de io::Result
a une
méthode expect
que vous pouvez utiliser. Si cette
instance de io::Result
a pour valeur la variante Err
, l'appel à expect
fera planter le programme et affichera le message que vous avez passé en
argument de expect
. Si l'appel à read_line
retourne une variante Err
, ce
sera probablement dû à une erreur du système d'exploitation. Si en revanche
read_line
a pour valeur la variante Ok
, expect
récupèrera le
contenu du Ok
, qui est le résultat de l'opération, et vous le retournera afin
que vous puissiez l'utiliser. Dans notre exemple, ce résultat est le nombre
d'octets de la saisie utilisateur.
Si on n'appelle pas expect
, le programme compilera, mais avec un
avertissement :
$ cargo build
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
warning: unused `Result` that must be used
--> src/main.rs:10:5
|
10 | io::stdin().read_line(&mut supposition);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
warning: `jeu_du_plus_ou_du_moins` (bin "jeu_du_plus_ou_du_moins") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
Rust nous prévient que l'on ne fait rien du Result
que nous fournit
read_line
, et que par conséquent notre programme ne gère pas une erreur
potentielle.
La meilleure façon de masquer cet avertissement est de réellement écrire le
code permettant de gérer l'erreur, mais dans notre cas on a seulement besoin de
faire planter le programme si un problème survient, on utilise donc expect
.
Nous verrons dans le chapitre 9 comment gérer
correctement les erreurs.
Afficher des valeurs grâce aux espaces réservés de println!
Mis à part l'accolade fermante, il ne nous reste plus qu'une seule ligne à étudier dans le code que nous avons pour l'instant :
use std::io;
fn main() {
println!("Devinez le nombre !");
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
Cette ligne affiche la chaîne de caractères qui contient maintenant ce que
l'utilisateur a saisi. La paire d'accolades {}
représente un espace réservé :
imaginez qu'il s'agit de pinces de crabes qui gardent la place d'une valeur.
Vous pouvez afficher plusieurs valeurs en utilisant des accolades : la première
paire d'accolades affichera la première valeur listée après la chaîne de
formatage, la deuxième paire d'accolades affichera la deuxième valeur, et ainsi
de suite. Pour afficher plusieurs valeurs en appelant println!
une seule
fois, on ferait comme ceci :
#![allow(unused)] fn main() { let x = 5; let y = 10; println!("x = {} et y = {}", x, y); }
Ce code afficherait x = 5 et y = 10
.
Test de la première partie
Pour tester notre début de programme, lançons-le à l'aide de la commande
cargo run
:
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Veuillez entrer un nombre.
6
Votre nombre : 6
À ce stade, la première partie de notre programme est terminée : nous avons récupéré la saisie du clavier et nous l'affichons à l'écran.
Générer le nombre secret
Maintenant, il nous faut générer un nombre secret que notre joueur va devoir
deviner. Ce nombre devra être différent à chaque fois pour qu'on puisse
s'amuser à y jouer plusieurs fois. Nous allons tirer au sort un nombre compris
entre 1 et 100 pour que le jeu ne soit pas trop difficile. Rust n'embarque pas
pour l'instant de fonctionnalité de génération de nombres aléatoires dans sa
bibliothèque standard. Cependant, l'équipe de Rust propose une
crate rand
qui offre la possibilité de le faire.
Étendre les fonctionnalités de Rust avec une crate
Souvenez-vous, une crate est un ensemble de fichiers de code source Rust. Le
projet sur lequel nous travaillons est une crate binaire, qui est un programme
exécutable. La crate rand
est une crate de bibliothèque, qui contient du
code qui peut être utilisé dans d'autres programmes, et qui ne peut pas être
exécuté tout seul.
La coordination des crates externes est un domaine dans lequel Cargo excelle.
Avant d'écrire le code qui utilisera rand
, il nous faut éditer le fichier
Cargo.toml pour y spécifier rand
en tant que dépendance. Ouvrez donc
maintenant ce fichier et ajoutez la ligne suivante à la fin, en dessous de
l'en-tête de section [dependencies]
que Cargo a créé pour vous. Assurez-vous
de spécifier rand
exactement comme dans le bout de code suivant, avec ce
numéro de version, ou sinon les exemples de code de ce tutoriel pourraient ne
pas fonctionner.
Fichier : Cargo.toml
rand = "0.8.3"
Dans le fichier Cargo.toml, tout ce qui suit une en-tête fait partie de cette
section, et ce jusqu'à ce qu'une autre section débute. Dans [dependencies]
,
vous indiquez à Cargo de quelles crates externes votre
projet dépend, et de quelle version de ces crates vous avez besoin.
Dans notre cas, on ajoute comme dépendance la crate rand
avec la version
sémantique 0.8.3
. Cargo arrive à interpréter le
versionnage sémantique (aussi appelé SemVer), qui
est une convention d'écriture de numéros de version. En réalité, 0.8.3
est
une abréviation pour ^0.8.3
, ce qui signifie “toute version ultérieure ou
égale à 0.8.3
mais strictement antérieure à 0.9.0
”. Cargo considère que ces
versions ont des API publiques compatibles avec la version 0.8.3
, et cette
indication garantit que vous obtiendrez la dernière version de correction qui
compilera encore avec le code de ce chapitre. Il n'est pas garanti que les
versions 0.9.0
et ultérieures aient la même API que celle utilisée dans les
exemples suivants.
Maintenant, sans apporter le moindre changement au code, lançons une compilation du projet, comme dans l'encart 2-2 :
$ cargo build
Updating crates.io index
Downloaded rand v0.8.3
Downloaded libc v0.2.86
Downloaded getrandom v0.2.2
Downloaded cfg-if v1.0.0
Downloaded ppv-lite86 v0.2.10
Downloaded rand_chacha v0.3.0
Downloaded rand_core v0.6.2
Compiling rand_core v0.6.2
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Il est possible que vous ne voyiez pas exactement les mêmes numéros de version, (mais ils seront compatibles avec votre code, grâce au versionnage sémantique !), différentes lignes (en fonction de votre système d'exploitation), et les lignes ne seront pas forcément affichées dans le même ordre.
Lorsque nous ajoutons une dépendance externe, Cargo récupère les dernières versions de tout ce dont cette dépendance a besoin depuis le registre, qui est une copie des données de Crates.io. Crates.io est là où les développeurs de l'écosystème Rust publient leurs projets open source afin de les rendre disponibles aux autres.
Une fois le registre mis à jour, Cargo lit la section [dependencies]
et se
charge de télécharger les crates qui y sont listés que vous n'avez pas encore
téléchargé. Dans notre cas, bien que nous n'ayons spécifié qu'une seule
dépendance, rand
, Cargo a aussi téléchargé d'autres crates dont dépend
rand
pour fonctionner. Une fois le téléchargement terminé des crates, Rust
les compile, puis compile notre projet avec les dépendances disponibles.
Si vous relancez tout de suite cargo build
sans changer quoi que ce soit, vous
n'obtiendrez rien d'autre que la ligne Finished
. Cargo sait qu'il a déjà
téléchargé et compilé les dépendances, et que vous n'avez rien changé dans votre
fichier Cargo.toml. Cargo sait aussi que vous n'avez rien changé dans votre
code, donc il ne le recompile pas non plus. Étant donné qu'il n'a rien à faire,
Cargo se termine tout simplement.
Si vous ouvrez le fichier src/main.rs, faites un changement très simple, enregistrez le fichier, et relancez la compilation, vous verrez s'afficher uniquement deux lignes :
$ cargo build
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
Ces lignes nous informent que Cargo a recompilé uniquement à cause de notre petit changement dans le fichier src/main.rs. Les dépendances n'ayant pas changé, Cargo sait qu'il peut simplement réutiliser ce qu'il a déjà téléchargé et compilé précédemment.
Assurer la reproductibilité des compilations avec le fichier Cargo.lock
Cargo embarque une fonctionnalité qui garantie que vous pouvez recompiler le
même artéfact à chaque fois que vous ou quelqu'un d'autre compile votre code :
Cargo va utiliser uniquement les versions de dépendances que vous avez
utilisées jusqu'à ce que vous indiquiez le contraire.
Par exemple, immaginons que la semaine prochaine, la version 0.8.4 de la
crate rand
est publiée, et qu'elle apporte une correction importante, mais
aussi qu'elle produit une régression qui va casser votre code. Pour éviter cela,
Rust crée le fichier Cargo.lock la première fois que vous utilisez
cargo build
, donc nous l'avons désormais dans le dossier
jeu_du_plus_ou_du_moins.
Quand vous compilez un projet pour la
première fois, Cargo détermine toutes les versions de dépendances qui
correspondent à vos critères et les écrit dans le fichier Cargo.lock. Quand
vous recompilerez votre projet plus tard, Cargo verra que le fichier
Cargo.lock existe et utilisera les versions précisées à l'intérieur au lieu
de recommencer à déterminer toutes les versions demandées.
Ceci vous permet d'avoir automatiquement des compilations reproductibles.
En d'autres termes, votre projet va rester sur la version 0.8.3
jusqu'à ce
que vous le mettiez à jour explicitement, grâce au fichier Cargo.lock.
Mettre à jour une crate vers sa nouvelle version
Lorsque vous souhaitez réellement mettre à jour une crate, Cargo vous fournit
la commande update
, qui va ignorer le fichier Cargo.lock et va rechercher
toutes les versions qui correspondent à vos critères dans Cargo.toml. Cargo
va ensuite écrire ces versions dans le fichier Cargo.lock. Sinon par défaut,
Cargo va rechercher uniquement les versions plus grandes que 0.8.3
et
inférieures à 0.9.0
. Si la crate rand
a été publiée en deux nouvelles
versions 0.8.4
et 0.9.0
, alors vous verrez ceci si vous lancez
cargo update
:
$ cargo update
Updating crates.io index
Updating rand v0.8.3 -> v0.8.4
Cargo ignore la version 0.9.0
. À partir de ce moment, vous pouvez aussi
constater un changement dans le fichier Cargo.lock indiquant que la version
de la crate rand
que vous utilisez maintenant est la 0.8.4
. Pour utiliser
rand
en version 0.9.0
ou toute autre version dans la série des 0.9.x
, il
vous faut mettre à jour le fichier Cargo.toml comme ceci :
[dependencies]
rand = "0.9.0"
La prochaine fois que vous lancerez cargo build
, Cargo mettra à jour son
registre de crates disponibles et réévaluera vos exigences vis-à-vis de rand
selon la nouvelle version que vous avez spécifiée.
Il y a encore plus à dire à propos de Cargo et de son écosystème que nous aborderons au chapitre 14, mais pour l'instant, c'est tout ce qu'il vous faut savoir. Cargo facilite la réutilisation des bibliothèques, pour que les Rustacés soient capables d'écrire des petits projets issus d'un assemblage d'un certain nombre de paquets.
Générer un nombre aléatoire
Commençons désormais à utiliser rand
pour générer un nombre à deviner. La
prochaine étape est de modifier src/main.rs comme dans l'encart 2-3.
Fichier : src/main.rs
use std::io;
use rand::Rng;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
println!("Le nombre secret est : {}", nombre_secret);
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
}
D'abord, nous avons ajouté la ligne use rand::Rng
. Le trait Rng
définit
les méthodes implémentées par les générateurs de nombres aléatoires, et ce
trait doit être accessible à notre code pour qu'on puisse utiliser ces
méthodes. Le chapitre 10 expliquera plus en détail les traits.
Ensuite, nous ajoutons deux lignes au milieu. A la première ligne, nous
appelons la fonction rand::thread_rng
qui nous fournit le générateur de
nombres aléatoires particulier que nous allons utiliser : il est propre au fil
d'exécution courant et généré par le système d'exploitation. Ensuite, nous
appelons la méthode gen_range
sur le générateur de nombres aléatoires. Cette
méthode est définie par le trait Rng
que nous avons importé avec
l'instruction use rand::Rng
. La méthode gen_range
prend une expression
d'intervalle en paramètre et génère un nombre aléatoire au sein de
l'intervalle. Le genre d'expression d'intervalle utilisé ici est de la forme
début..fin
et inclut la borne inférieure mais exclut la borne supérieure,
nous avons donc besoin de préciser 1..101
pour demander un nombre entre 1
et 100. De manière équivalente, nous pourrions également passer l'intervalle
fermé 1..=100
Remarque : vous ne pourrez pas deviner quels traits, méthodes et fonctions utiliser avec une crate, donc chaque crate a une documentation qui donne des indications sur son utilisation. Une autre fonctionnalité intéressante de Cargo est que vous pouvez utiliser la commande
cargo doc --open
, qui va construire localement la documentation intégrée par toutes vos dépendances et va l'ouvrir dans votre navigateur. Si vous vous intéressez à d'autres fonctionnalités de la craterand
, par exemple, vous pouvez lancercargo doc --open
et cliquer surrand
dans la barre latérale sur la gauche.
La seconde nouvelle ligne affiche le nombre secret. C'est pratique lors du développement pour pouvoir le tester, mais nous l'enlèverons dans la version finale. Ce n'est pas vraiment un jeu si le programme affiche la réponse dès qu'il démarre !
Essayez de lancer le programme plusieurs fois :
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Le nombre secret est : 7
Veuillez entrer un nombre.
4
Votre nombre : 4
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Le nombre secret est : 83
Veuillez entrer un nombre.
5
Votre nombre : 5
Vous devriez obtenir des nombres aléatoires différents, et ils devraient être tous compris entre 1 et 100. Beau travail !
Comparer le nombre saisi au nombre secret
Maintenant que nous avons une saisie utilisateur et un nombre aléatoire, nous pouvons les comparer. Cette étape est écrite dans l'encart 2-4. Sachez toutefois que le code ne se compile pas encore, nous allons l'expliquer par la suite.
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
// -- partie masquée ici --
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
println!("Le nombre secret est : {}", nombre_secret);
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
println!("Votre nombre : {}", supposition);
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => println!("Vous avez gagné !"),
}
}
Premièrement, nous ajoutons une autre instruction use
, qui importe
std::cmp::Ordering
à portée de notre code depuis la bibliothèque standard.
Le type Ordering
est une autre énumération et a les variantes Less
(inférieur), Greater
(supérieur) et Equal
(égal). Ce sont les trois
issues possibles lorsqu'on compare deux valeurs.
Ensuite, nous ajoutons cinq nouvelles lignes à la fin qui utilisent le type
Ordering
. La méthode cmp
compare deux valeurs et peut être appelée sur
tout ce qui peut être comparé. Elle prend en paramètre une référence de ce qu'on
veut comparer : ici, nous voulons comparer supposition
et nombre_secret
.
Ensuite, cela retourne une variante de l'énumération Ordering
que nous avons
importée avec l'instruction use
. Nous utilisons une expression
match
pour décider quoi faire ensuite en fonction de
quelle variante de Ordering
a été retournée à l'appel de cmp
avec
supposition
et nombre_secret
.
Une expression match
est composée de branches. Une branche est constituée
d'un motif (pattern) avec lequel elle doit correspondre et du code qui sera
exécuté si la valeur donnée au match
correspond bien au motif de cette
branche. Rust prend la valeur donnée à match
et la compare au motif de chaque
branche à tour de rôle. Les motifs et la structure de contrôle match
sont des
fonctionnalités puissantes de Rust qui vous permettent de décrire une multitude
de scénarios que votre code peut rencontrer et de s'assurer que vous les gérez
toutes. Ces fonctionnalités seront expliquées plus en détail respectivement
dans le chapitre 6 et le chapitre 18.
Voyons un exemple avec l'expression match
que nous avons utilisé ici. Disons
que l'utilisateur a saisi le nombre 50 et que le nombre secret généré
aléatoirement a cette fois-ci comme valeur 38. Quand le code compare 50 à 38,
la méthode cmp
va retourner Ordering::Greater
, car 50 est plus grand
que 38. L'expression match
obtient la valeur Ordering::Greater
et commence
à vérifier le motif de chaque branche. Elle consulte le motif de la première
branche, Ordering::Less
et remarque que la valeur Ordering::Greater
ne
correspond pas au motif Ordering::Less
; elle ignore donc le code de cette
branche et passe à la suivante. Le motif de la branche suivante est
Ordering::Greater
, qui correspond à Ordering::Greater
! Le code associé à
cette branche va être exécuté et va afficher à l'écran C'est moins !
.
L'expression match
se termine ensuite, car elle n'a pas besoin de consulter
les autres branches de ce scénario.
Cependant, notre code dans l'encart 2-4 ne compile pas encore. Essayons de le faire :
$ cargo build
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_core v0.6.2
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
error[E0308]: mismatched types
--> src/main.rs:22:21
|
22 | match supposition.cmp(&nombre_secret) {
| ^^^^^^^^^^^^^^ expected struct `String`, found integer
|
= note: expected reference `&String`
found reference `&{integer}`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `jeu_du_plus_ou_du_moins` due to previous error
error[E0283]: type annotations needed for `{integer}`
--> src/main.rs:8:44
|
8 | let nombre_secret = rand::thread_rng().gen_range(1..101);
| ------------- ^^^^^^^^^ cannot infer type for type `{integer}`
| |
| consider giving `nombre_secret` a type
|
= note: multiple `impl`s satisfying `{integer}: SampleUniform` found in the `rand` crate:
- impl SampleUniform for i128;
- impl SampleUniform for i16;
- impl SampleUniform for i32;
- impl SampleUniform for i64;
and 8 more
note: required by a bound in `gen_range`
--> /Users/carolnichols/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.8.3/src/rng.rs:129:12
|
129 | T: SampleUniform,
| ^^^^^^^^^^^^^ required by this bound in `gen_range`
help: consider specifying the type arguments in the function call
|
8 | let nombre_secret = rand::thread_rng().gen_range::<T, R>(1..101);
| ++++++++
Some errors have detailed explanations: E0283, E0308.
For more information about an error, try `rustc --explain E0283`.
error: could not compile `guessing_game` due to 2 previous errors
Le message d'erreur nous indique que nous sommes dans un cas de types non
compatibles (mismatched types). Rust a un système de types fort et statique.
Cependant, il a aussi une fonctionnalité d'inférence de type. Quand nous avons
écrit let mut supposition = String::new()
, Rust a pu en déduire que
supposition
devait être une String
et ne nous a pas demandé d'écrire le
type. D'autre part, nombre_secret
est d'un type de nombre. Quelques types de
nombres de Rust peuvent avoir une valeur entre 1 et 100 : i32
, un nombre
entier encodé sur 32 bits ; u32
, un nombre entier de 32 bits non signé
(positif ou nul) ; i64
, un nombre entier encodé sur 64 bits ; parmi tant
d'autres. Rust utilise par défaut un i32
, qui est le type de nombre_secret
,
à moins que vous précisiez quelque part une information de type qui amènerait
Rust à inférer un type de nombre différent. La raison de cette erreur est que
Rust ne peut pas comparer une chaîne de caractères à un nombre.
Au bout du compte, nous voulons convertir la String
que le programme récupère
de la saisie utilisateur en un nombre, pour qu'on puisse la comparer
numériquement au nombre secret. Nous allons faire ceci en ajoutant cette ligne
supplémentaire dans le corps de la fonction main
:
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
println!("Le nombre secret est : {}", nombre_secret);
println!("Veuillez entrer un nombre.");
// -- partie masquée ici --
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
let supposition: u32 = supposition.trim().parse().expect("Veuillez entrer un nombre !");
println!("Votre nombre : {}", supposition);
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => println!("Vous avez gagné !"),
}
}
La nouvelle ligne est :
let supposition: u32 = supposition.trim().parse().expect("Veuillez entrer un nombre !");
Nous créons une variable qui s'appelle supposition
. Mais attendez, le
programme n'a-t-il pas déjà une variable qui s'appelle supposition
?
C'est le cas, mais heureusement Rust nous permet de masquer la valeur
précédente de supposition
avec une nouvelle.
Le masquage (shadowing) nous permet de réutiliser le nom de variable
supposition
, plutôt que de nous forcer à créer deux variables distinctes,
telles que supposition_str
et supposition
par exemple.
Nous verrons cela plus en détails au chapitre 3, mais pour le moment cette
fonctionnalité est souvent utilisée dans des situations où on veut convertir
une valeur d'un type à un autre.
Nous lions cette nouvelle variable à l'expression supposition.trim().parse()
.
Le supposition
dans l'expression se réfère à la variable supposition
initiale qui contenait la saisie utilisateur en tant que chaîne de caractères.
String
contenant la saisie utilisateur. La méthode trim
sur une instance
de String
va enlever les espaces et autres whitespaces au début et à la
fin, ce que nous devons faire pour comparer la chaîne au u32
, qui ne peut
être constitué que de chiffres. L'utilisateur doit appuyer sur
entrée pour mettre fin à read_line
et
récupérer leur supposition, ce qui va rajouter un caractère de fin de ligne à
la chaîne de caractères. Par exemple, si l'utilisateur écrit
5 et appuie sur entrée
, supposition
aura alors cette valeur : 5\n
.
Le \n
représente une fin de ligne (à noter que sur Windows, appuyer sur
entrée résulte en un retour chariot suivi d'une
fin de ligne, \r\n
). La méthode trim
enlève \n
et \r\n
, il ne reste donc
plus que 5
.
La méthode parse
des chaînes de caractères interprète
une chaîne de caractères en une sorte de nombre. Comme cette méthode peut
interpréter plusieurs types de nombres, nous devons indiquer à Rust le type
exact de nombre que nous voulons en utilisant let supposition: u32
.
Le deux-points (:
) après supposition
indique à Rust que nous voulons
préciser le type de la variable.
Rust embarque quelques types de nombres ; le u32
utilisé ici est un
entier non signé sur 32 bits.
C'est un bon choix par défaut pour un petit nombre positif.
Vous découvrirez d'autres types de nombres dans le chapitre 3.
De plus, l'annotation u32
dans ce programme d'exemple et la
comparaison avec nombre_secret
permet à Rust d'en déduire que nombre_secret
doit être lui aussi un u32
. Donc maintenant, la comparaison se fera
entre deux valeurs du même type !
La méthode parse
va fonctionner uniquement sur des caractères qui peuvent
être logiquement convertis en nombres et donc peut facilement mener à une
erreur. Si par exemple, le texte contient A👍%
, il ne sera pas possible de le
convertir en nombre. Comme elle peut échouer, la méthode parse
retourne un
type Result
, comme celui que la méthode read_line
retourne (comme nous
l'avons vu plus tôt dans “Gérer les erreurs potentielles avec le type
Result
”).
Nous allons gérer ce Result
de la même manière, avec à nouveau la méthode
expect
. Si parse
retourne une variante Err
de Result
car elle ne peut
pas créer un nombre à partir de la chaîne de caractères, l'appel à
expect
va faire planter le jeu et va afficher le message que nous lui avons
passé en paramètre. Si parse
arrive à convertir la chaîne de caractères en
nombre, alors elle retournera la variante Ok
de Result
, et expect
va
retourner le nombre qu'il nous faut qui est stocké dans la variante Ok
.
Exécutons ce programme, maintenant !
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Le nombre secret est : 58
Veuillez entrer un nombre.
76
Votre nombre : 76
C'est moins !
Très bien ! Même si des espaces ont été ajoutées avant la supposition, le programme a quand même compris que l'utilisateur a saisi 76. Lancez le programme plusieurs fois pour vérifier qu'il se comporte correctement avec différentes saisies : devinez le nombre correctement, saisissez un nombre qui est trop grand, et saisissez un nombre qui est trop petit.
La majeure partie du jeu fonctionne désormais, mais l'utilisateur ne peut faire qu'une seule supposition. Corrigeons cela en ajoutant une boucle !
Permettre plusieurs suppositions avec les boucles
Le mot-clé loop
crée une boucle infinie. Nous allons ajouter une boucle pour
donner aux utilisateurs plus de chances de deviner le nombre :
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
// -- partie masquée ici --
println!("Le nombre secret est : {}", nombre_secret);
loop {
println!("Veuillez entrer un nombre.");
// -- partie masquée ici --
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
let supposition: u32 = supposition.trim().parse().expect("Veuillez entrer un nombre !");
println!("Votre nombre : {}", supposition);
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => println!("Vous avez gagné !"),
}
}
}
Comme vous pouvez le remarquer, nous avons déplacé dans une boucle tout le code de l'invite à entrer le nombre. Assurez-vous d'indenter correctement les lignes dans la boucle avec quatre nouvelles espaces pour chacune, et lancez à nouveau le programme. Le programme va désormais demander un nombre à l'infini, ce qui est un nouveau problème. Il n'est pas possible pour l'utilisateur de l'arrêter !
L'utilisateur pourrait quand même interrompre le programme en utilisant le raccourci clavier ctrl-c. Mais il y a une autre façon d'échapper à ce monstre insatiable, comme nous l'avons abordé dans la partie “Comparer le nombre saisi au nombre secret” : si l'utilisateur saisit quelque chose qui n'est pas un nombre, le programme va planter. Nous pouvons procéder ainsi pour permettre à l'utilisateur de quitter, comme ci-dessous :
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 1.50 secs
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Le nombre secret est : 59
Veuillez entrer un nombre.
45
Votre nombre : 45
C'est plus !
Veuillez entrer un nombre.
60
Votre nombre : 60
C'est moins !
Veuillez entrer un nombre.
59
Votre nombre : 59
Vous avez gagné !
Veuillez entrer un nombre.
quitter
thread 'main' panicked at 'Veuillez entrer un nombre !: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47
note: Run with `RUST_BACKTRACE=1` for a backtrace
Taper quitter
va bien fermer le jeu, mais comme vous pouvez le remarquer,
toute autre saisie qui n'est pas un nombre le ferait aussi. Ce mécanisme laisse
franchement à désirer ; nous voudrions que le jeu s'arrête aussi lorsque le bon
nombre est deviné.
Arrêter le programme après avoir gagné
Faisons en sorte que le jeu s'arrête quand le joueur gagne en ajoutant
l'instruction break
:
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
println!("Le nombre secret est : {}", nombre_secret);
loop {
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
let supposition: u32 = supposition.trim().parse().expect("Veuillez entrer un nombre !");
println!("Votre nombre : {}", supposition);
// -- partie masquée ici --
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => {
println!("Vous avez gagné !");
break;
}
}
}
}
Ajouter la ligne break
après Vous avez gagné !
fait sortir le programme de
la boucle quand le joueur a correctement deviné le nombre secret. Et quitter la
boucle veut aussi dire terminer le programme, car ici la boucle est la dernière
partie de main
.
Gérer les saisies invalides
Pour améliorer le comportement du jeu, plutôt que de faire planter le programme
quand l'utilisateur saisit quelque chose qui n'est pas un nombre, faisons en
sorte que le jeu ignore ce qui n'est pas un nombre afin que l'utilisateur puisse
continuer à essayer de deviner. Nous pouvons faire ceci en modifiant la ligne où
supposition
est converti d'une String
en un u32
, comme dans l'encart 2-5 :
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
println!("Le nombre secret est : {}", nombre_secret);
loop {
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
// -- partie masquée ici --
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
let supposition: u32 = match supposition.trim().parse() {
Ok(nombre) => nombre,
Err(_) => continue,
};
println!("Votre nombre : {}", supposition);
// -- partie masquée ici --
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => {
println!("Vous avez gagné !");
break;
}
}
}
}
Nous remplaçons un appel à expect
par une expression match
pour passer
d'une erreur qui fait planter le programme à une erreur proprement gérée.
N'oubliez pas que parse
retourne un type Result
et que Result
est une
énumération qui a pour variantes Ok
et Err
. Nous utilisons ici une
expression match
comme nous l'avons déjà fait avec le résultat de type
Ordering
de la méthode cmp
.
Si parse
arrive à convertir la chaîne de caractères en nombre, cela va
retourner la variante Ok
qui contient le nombre qui en résulte. Cette variante
va correspondre au motif de la première branche, et l'expression match
va
simplement retourner la valeur de nombre
que parse
a trouvée et qu'elle a
mise dans la variante Ok
.
Ce nombre va se retrouver là où nous en avons besoin,
dans la variable supposition
que nous sommes en train de créer.
Si parse
n'arrive pas à convertir la chaîne de caractères en nombre, elle
va retourner la variante Err
qui contient plus d'informations sur l'erreur. La
variante Err
ne correspond pas au motif Ok(nombre)
de la première branche,
mais elle correspond au motif Err(_)
de la seconde branche. Le tiret bas,
_
, est une valeur passe-partout ; dans notre exemple, nous disons
que nous voulons correspondre à toutes les valeurs de Err
, peu importe quelle
information elles ont à l'intérieur d'elles-mêmes. Donc le programme va exécuter
le code de la seconde branche, continue
, qui indique au programme de se rendre
à la prochaine itération de loop
et de demander un nouveau nombre. Ainsi, le
programme ignore toutes les erreurs que parse
pourrait rencontrer !
Maintenant, le programme devrait fonctionner correctement. Essayons-le :
$ cargo run
Compiling jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
Finished dev [unoptimized + debuginfo] target(s) in 4.45s
Running `target/debug/jeu_du_plus_ou_du_moins`
Devinez le nombre !
Le nombre secret est : 61
Veuillez entrer un nombre.
10
Votre nombre : 10
C'est plus !
Veuillez entrer un nombre.
99
Votre nombre : 99
C'est moins !
Veuillez entrer un nombre.
foo
Veuillez entrer un nombre.
61
Votre nombre : 61
Vous avez gagné !
Super ! Avec notre petite touche finale, nous avons fini notre jeu du plus ou du
moins. Rappelez-vous que le programme affiche toujours le nombre secret. C'était
pratique pour les tests, mais cela gâche le jeu. Supprimons le println!
qui
affiche le nombre secret. L'encart 2-6 représente le code final.
Fichier : src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Devinez le nombre !");
let nombre_secret = rand::thread_rng().gen_range(1..101);
loop {
println!("Veuillez entrer un nombre.");
let mut supposition = String::new();
io::stdin()
.read_line(&mut supposition)
.expect("Échec de la lecture de l'entrée utilisateur");
let supposition: u32 = match supposition.trim().parse() {
Ok(nombre) => nombre,
Err(_) => continue,
};
println!("Votre nombre : {}", supposition);
match supposition.cmp(&nombre_secret) {
Ordering::Less => println!("C'est plus !"),
Ordering::Greater => println!("C'est moins !"),
Ordering::Equal => {
println!("Vous avez gagné !");
break;
}
}
}
}
Résumé
Si vous êtes arrivé jusqu'ici, c'est que vous avez construit avec succès le jeu du plus ou du moins. Félicitations !
Ce projet était une mise en pratique pour vous initier à de nombreux concepts de
Rust : let
, match
, les méthodes, les fonctions associées, l'utilisation de
crates externes, et bien plus. Dans les prochains chapitres, vous allez en
apprendre plus sur ces concepts. Le chapitre 3 va traiter des concepts utilisés
par la plupart des langages de programmation, comme les variables, les types de
données, et les fonctions, et vous montrera comment les utiliser avec Rust. Le
chapitre 4 expliquera la possession (ownership), qui est une fonctionnalité
qui distingue Rust des autres langages. Le chapitre 5 abordera les structures et
les syntaxes des méthodes, et le chapitre 6 expliquera comment les énumérations
fonctionnent.