Introduction
Bienvenue dans Bringing Paths Into Scope With the Use Keyword. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.
Dans ce laboratoire, nous apprenons à porter des chemins en portée en utilisant le mot-clé use pour créer des raccourcis pour appeler des fonctions et des modules.
Bringing Paths into Scope with the use Keyword
Devoir écrire les chemins pour appeler des fonctions peut paraître pratique et répétitif. Dans la Liste 7-7, que nous ayons choisi le chemin absolu ou relatif vers la fonction add_to_waitlist, chaque fois que nous avons voulu appeler add_to_waitlist, nous avons dû également spécifier front_of_house et hosting. Heureusement, il existe un moyen de simplifier ce processus : nous pouvons créer un raccourci pour un chemin une fois avec le mot-clé use, puis utiliser le nom plus court partout ailleurs dans la portée.
Dans la Liste 7-11, nous ramenons le module crate::front_of_house::hosting dans la portée de la fonction eat_at_restaurant afin que nous n'ayons qu'à spécifier hosting::add_to_waitlist pour appeler la fonction add_to_waitlist dans eat_at_restaurant.
Nom de fichier : src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Liste 7-11 : Amener un module dans la portée avec use
Ajouter use et un chemin dans une portée est similaire à créer un lien symbolique dans le système de fichiers. En ajoutant use crate::front_of_house::hosting dans la racine du crate, hosting est désormais un nom valide dans cette portée, tout comme si le module hosting avait été défini dans la racine du crate. Les chemins ramenés dans la portée avec use vérifient également la confidentialité, comme tous les autres chemins.
Notez que use ne crée le raccourci que pour la portée particulière dans laquelle use apparaît. La Liste 7-12 déplace la fonction eat_at_restaurant dans un nouveau module enfant nommé customer, qui est alors une portée différente de l'énoncé use, de sorte que le corps de la fonction ne compilera pas.
Nom de fichier : src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
Liste 7-12 : Un énoncé use n'est valable que dans la portée où il se trouve.
L'erreur du compilateur montre que le raccourci n'est plus valable dans le module customer :
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Remarquez qu'il y a également un avertissement indiquant que use n'est plus utilisé dans sa portée! Pour résoudre ce problème, déplacez également use dans le module customer, ou faites référence au raccourci dans le module parent avec super::hosting dans le module enfant customer.
Creating Idiomatic use Paths
Dans la Liste 7-11, vous avez peut-être été curieux de savoir pourquoi nous avons spécifié use crate::front_of_house::hosting puis appelé hosting::add_to_waitlist dans eat_at_restaurant, plutôt que de spécifier le chemin use jusqu'à la fonction add_to_waitlist pour obtenir le même résultat, comme dans la Liste 7-13.
Nom de fichier : src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
Liste 7-13 : Amener la fonction add_to_waitlist dans la portée avec use, ce qui n'est pas idiomatique
Bien que les Listes 7-11 et 7-13 accomplissent la même tâche, la Liste 7-11 est la manière idiomatique d'amener une fonction dans la portée avec use. Amener le module parent de la fonction dans la portée avec use signifie que nous devons spécifier le module parent lorsqu'appelons la fonction. Spécifier le module parent lorsqu'on appelle la fonction montre clairement que la fonction n'est pas définie localement tout en réduisant au minimum la répétition du chemin complet. Le code de la Liste 7-13 est ambigu quant à la définition de add_to_waitlist.
D'un autre côté, lorsqu'on amène des structs, des enums et autres éléments avec use, il est idiomatique de spécifier le chemin complet. La Liste 7-14 montre la manière idiomatique d'amener la struct HashMap de la bibliothèque standard dans la portée d'un crate binaire.
Nom de fichier : src/main.rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
Liste 7-14 : Amener HashMap dans la portée d'une manière idiomatique
Il n'y a pas de raison forte derrière cet idiome : c'est simplement la convention qui est apparue, et les gens sont habitués à lire et à écrire du code Rust de cette manière.
L'exception à cet idiome est si nous amenons deux éléments avec le même nom dans la portée avec des instructions use, car Rust ne permet pas cela. La Liste 7-15 montre comment amener deux types Result dans la portée qui ont le même nom mais des modules parents différents, et comment y faire référence.
Nom de fichier : src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
--snip--
}
fn function2() -> io::Result<()> {
--snip--
}
Liste 7-15 : Amener deux types avec le même nom dans la même portée nécessite d'utiliser leurs modules parents.
Comme vous pouvez le voir, en utilisant les modules parents, on distingue les deux types Result. Si au lieu de cela nous spécifions use std::fmt::Result et use std::io::Result, nous aurons deux types Result dans la même portée, et Rust ne saura pas lequel nous voulons lorsque nous utilisons Result.
Providing New Names with the as Keyword
Il existe une autre solution au problème d'amener deux types de même nom dans la même portée avec use : après le chemin, nous pouvons spécifier as et un nouveau nom local, ou alias, pour le type. La Liste 7-16 montre une autre manière d'écrire le code de la Liste 7-15 en renommant l'un des deux types Result à l'aide de as.
Nom de fichier : src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
--snip--
}
fn function2() -> IoResult<()> {
--snip--
}
Liste 7-16 : Renommer un type lorsqu'il est amené dans la portée avec le mot-clé as
Dans la deuxième instruction use, nous avons choisi le nouveau nom IoResult pour le type std::io::Result, qui ne rentrera pas en conflit avec le Result de std::fmt que nous avons également amené dans la portée. Les Listes 7-15 et 7-16 sont considérées comme idiomatiques, donc le choix est laissé à vous!
Re-exporting Names with pub use
Lorsque nous amenons un nom dans la portée avec le mot-clé use, le nom disponible dans la nouvelle portée est privé. Pour permettre au code qui appelle notre code de faire référence à ce nom comme s'il avait été défini dans la portée de ce code, nous pouvons combiner pub et use. Cette technique est appelée re-exportation car nous amenons un élément dans la portée mais nous le rendons également disponible pour que les autres le rapportent dans leur portée.
La Liste 7-17 montre le code de la Liste 7-11 avec use dans le module racine remplacé par pub use.
Nom de fichier : src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Liste 7-17 : Rendre un nom disponible pour tout code à utiliser à partir d'une nouvelle portée avec pub use
Avant ce changement, le code externe aurait dû appeler la fonction add_to_waitlist en utilisant le chemin restaurant::front_of_house::hosting::add_to_waitlist(). Maintenant que ce pub use a re-exporté le module hosting à partir du module racine, le code externe peut utiliser le chemin restaurant::hosting::add_to_waitlist() à la place.
La re-exportation est utile lorsque la structure interne de votre code est différente de la manière dont les programmeurs appelant votre code pensent au domaine. Par exemple, dans cette métaphore du restaurant, les personnes qui gèrent le restaurant pensent à "l'avant de la maison" et "l'arrière de la maison". Mais les clients qui visitent un restaurant probablement ne penseront pas aux parties du restaurant de cette manière. Avec pub use, nous pouvons écrire notre code avec une structure mais exposer une structure différente. En faisant cela, nous rendons notre bibliothèque bien organisée pour les programmeurs travaillant sur la bibliothèque et les programmeurs appelant la bibliothèque. Nous examinerons un autre exemple de pub use et comment cela affecte la documentation de votre crate dans "Exporting a Convenient Public API with pub use".
Using External Packages
Dans le Chapitre 2, nous avons programmé un projet de jeu de devinette qui utilisait un package externe appelé rand pour obtenir des nombres aléatoires. Pour utiliser rand dans notre projet, nous avons ajouté cette ligne à Cargo.toml :
Nom de fichier : Cargo.toml
rand = "0.8.5"
Ajouter rand comme dépendance dans Cargo.toml indique à Cargo de télécharger le package rand et toutes ses dépendances depuis https://crates.io, et de rendre rand disponible pour notre projet.
Ensuite, pour amener les définitions de rand dans la portée de notre package, nous avons ajouté une ligne use commençant par le nom de la crate, rand, et avons listé les éléments que nous voulions amener dans la portée. Rappelez-vous que dans "Generating a Random Number", nous avons amené la trait Rng dans la portée et avons appelé la fonction rand::thread_rng :
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
Les membres de la communauté Rust ont mis à disposition de nombreux packages sur https://crates.io, et inclure l'un d'entre eux dans votre package implique les mêmes étapes : les lister dans le fichier Cargo.toml de votre package et utiliser use pour amener des éléments de leurs crates dans la portée.
Notez que la bibliothèque standard std est également une crate externe à notre package. Étant donné que la bibliothèque standard est distribuée avec le langage Rust, nous n'avons pas besoin de modifier Cargo.toml pour inclure std. Mais nous devons la référencer avec use pour amener des éléments de là dans la portée de notre package. Par exemple, avec HashMap nous utiliserions cette ligne :
use std::collections::HashMap;
Il s'agit d'un chemin absolu commençant par std, le nom de la crate de la bibliothèque standard.
Using Nested Paths to Clean Up Large use Lists
Si nous utilisons plusieurs éléments définis dans la même crate ou le même module, énumérer chaque élément sur une ligne distincte peut prendre beaucoup d'espace vertical dans nos fichiers. Par exemple, ces deux instructions use que nous avions dans le jeu de devinette de la Liste 2-4 amènent des éléments de std dans la portée :
Nom de fichier : src/main.rs
--snip--
use std::cmp::Ordering;
use std::io;
--snip--
Au lieu de cela, nous pouvons utiliser des chemins imbriqués pour amener les mêmes éléments dans la portée en une seule ligne. Nous le faisons en spécifiant la partie commune du chemin, suivie de deux points, puis des accolades autour d'une liste des parties du chemin qui diffèrent, comme montré dans la Liste 7-18.
Nom de fichier : src/main.rs
--snip--
use std::{cmp::Ordering, io};
--snip--
Liste 7-18 : Spécifier un chemin imbriqué pour amener plusieurs éléments avec le même préfixe dans la portée
Dans des programmes plus grands, amener de nombreux éléments dans la portée à partir de la même crate ou module en utilisant des chemins imbriqués peut réduire considérablement le nombre d'instructions use séparées nécessaires!
Nous pouvons utiliser un chemin imbriqué à n'importe quel niveau dans un chemin, ce qui est utile lorsqu'on combine deux instructions use qui partagent un sous-chemin. Par exemple, la Liste 7-19 montre deux instructions use : l'une qui amène std::io dans la portée et l'autre qui amène std::io::Write dans la portée.
Nom de fichier : src/lib.rs
use std::io;
use std::io::Write;
Liste 7-19 : Deux instructions use dont l'une est un sous-chemin de l'autre
La partie commune de ces deux chemins est std::io, et c'est le premier chemin complet. Pour fusionner ces deux chemins en une seule instruction use, nous pouvons utiliser self dans le chemin imbriqué, comme montré dans la Liste 7-20.
Nom de fichier : src/lib.rs
use std::io::{self, Write};
Liste 7-20 : Combiner les chemins de la Liste 7-19 en une seule instruction use
Cette ligne amène std::io et std::io::Write dans la portée.
The Glob Operator
Si nous voulons amener tous les éléments publics définis dans un chemin dans la portée, nous pouvons spécifier ce chemin suivi de l'opérateur * de type glob :
use std::collections::*;
Cette instruction use amène tous les éléments publics définis dans std::collections dans la portée actuelle. Faites attention lorsqu'on utilise l'opérateur glob! Le glob peut rendre plus difficile de savoir quels noms sont dans la portée et où un nom utilisé dans votre programme a été défini.
L'opérateur glob est souvent utilisé lors des tests pour amener tout ce qui est à tester dans le module tests ; nous en parlerons dans "Comment écrire des tests". L'opérateur glob est également parfois utilisé comme partie du motif de préambule : consultez la documentation de la bibliothèque standard pour en savoir plus sur ce motif.
Résumé
Félicitations! Vous avez terminé le laboratoire Bringing Paths Into Scope With the Use Keyword. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.