Que de questions
unwrap
unwrap
est une méthode des Result<T, E>
(ou des Option<T>
, y’a la même) de Rust, une structure qui peut contenir ou bien un résultat (Ok()
) ou une erreur (Err()
).
Si le résultat contient quelque chose, alors unwrap
va retourner ce qu’il contient, en “retirant la couche” qu’était le Result
, d’où le nom. Par exemple :
let x = Ok("zeste");
assert_eq!(x.unwrap(), "zeste");
Par contre, si le Result
est en erreur (Err
), unwrap
va paniquer, c’est pourquoi on préfère utiliser unwrap_or
qui permet de fournir une alternative dans ce cas précis, ou encore mieux un match
pour gérer l’erreur — mais je diverge, c’est juste pour info.
let x: Result<&str, &str> = Err("emergency failure");
x.unwrap();
assert_eq!(x.unwrap_or("zeste"), "zeste");
match x {
Ok(s) =>
Err(e) =>
}
Ici :
let mut input_line = String::new();
io::stdin().read_line(&mut input_line).unwrap();
…la méthode read_line
retourne un Result
(qui va être Ok
si la lecture de l’entrée standard a réussi, et Err
dans le cas contraire). On utilise unwrap
en partant du principe que ça va marcher, essentiellement car on est dans un cas où si ça plante pour ça, on s’en fout un peu. Mais là, si la lecture de stdin
échoue, le programme va planter.
À noter qu’ici, on ignore le retour de read_line
car cette fonction va modifier la variable directement par référence (pour ça qu’on lui passe un pointeur mutable en argument) — le Result
qu’elle retourne contient en fait le nombre d’octets lus, et il faut dire qu’on s’en fiche un peu. On utilise uniquement unwrap
pour que le programme panique si la lecture échoue. Mais dans la majorité des cas (on va d’ailleurs en voir un plus bas) on utilise unwrap
pour récupérer ce que la méthode retourne .
trim
trim
est une méthode de String
qui permet de retirer les nouvelles lignes, tabulations, espaces, etc., des extrémités d’une chaîne de caractère. Par exemple :
let s = " Zeste de\tsavoir\t\n";
assert_eq!("Zeste de\tsavoir", s.trim());
C’est une méthode très classique qu’on retrouve dans beaucoup d’autres langages de programmation sous le même nom (sauf Python où elle s’appelle strip
).
to_string
La méthode to_string
est une méthode commune (elle vient du trait ToString
et est implémentée par la majorité des types standard de Rust) qui permet de transformer une structure en une String
. Par exemple :
let i = 5;
let five = String::from("5");
assert_eq!(five, i.to_string());
Dans le cas présent :
let enemy_1 = input_line.trim().to_string();
…ça va surtout servir à convertir le retour de trim
, qu’est une &str
, en String
, afin d’éviter des problèmes d'ownership. Si tu ne maîtrises pas trop ça, je t’invite à (re)lire la partie concernée du guide, car en gros, &str
est le pendant emprunté (borrowed) de String
.
($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
Le code complet était :
macro_rules! parse_input {
($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
}
macro_rules!
est une macro qui sert à définir une… macro. (Plus généralement, en Rust, toute instruction qui se termine par un !
est une macro.) En gros, c’est un outil en Rust qui sert à faire de la génération de code avant la compilation, pour simplifier l’écriture de parties répétitives (ça rappelle un peu le pré-processeur en C ou C++).
C’est une fonctionnalité assez avancée de Rust (d’ailleurs elle est présentée presque à la fin du Rust Book). Ici, quand on utilise la macro, Rust va, avant la compilation, transformer ceci :
let dist_2 = parse_input!(input_line, i32);
en cela :
let dist_2 = input_line.trim().parse::<i32>().unwrap()
…ce qui concrètement va utiliser la méthode parse
pour convertir la chaîne de caractères entrée en i32
(un nombre positif ou négatif sur 32 bits). On retrouve d’ailleurs unwrap
, car parse
retourne également un Result
(par conséquent, si l’utilisateur entre autre chose qu’un nombre, le programme va paniquer, vu qu’on utilise pas unwrap_or
ou un pattern matching pour gérer l’éventuelle erreur).
Le langage de création de macros est un peu complexe, mais pour l’idée, si on décortique cette macro toute simple, ça va donner quelque chose comme ça :
macro_rules! parse_input {
(
$x:expr,
$t:ident
)
=> ($x.trim().parse::<$t>().unwrap())
}
Toute la puissance des macros se fait sentir quand elles peuvent générer du code plus complexe, avec des boucles ou similaire. Par exemple, tu as peut-être déjà croisé la macro vec!
qui permet de créer un vecteur (une liste). En fait, elle va transformer le code qu’elle reçoit ainsi :
let list = vec![1, 2, 3];
let list = {
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
Et le premier est, on l’admettra, bien plus agréable à écrire .
Note subsidiaire
Pour information, tu peux colorer le code Rust en précisant le langage après les trois accents grave :