Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Amener des chemins dans la portée avec le mot-clé use

Devoir écrire les chemins pour appeler des fonctions peut sembler peu pratique et répétitif. Dans le listing 7-7, que nous ayons choisi le chemin absolu ou relatif vers la fonction add_to_waitlist, chaque fois que nous voulions appeler add_to_waitlist, nous devions aussi spécifier front_of_house et hosting. Heureusement, il existe un moyen de simplifier ce processus : nous pouvons créer un raccourci vers un chemin avec le mot-clé use une seule fois, puis utiliser le nom plus court partout ailleurs dans la portée.

Dans le listing 7-11, nous amenons le module crate::front_of_house::hosting dans la portée de la fonction eat_at_restaurant afin de n’avoir qu’à spécifier hosting::add_to_waitlist pour appeler la fonction add_to_waitlist dans eat_at_restaurant.

Filename: 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();
}
Listing 7-11: Bringing a module into scope with use

Ajouter use et un chemin dans une portée est similaire à la création d’un lien symbolique dans le système de fichiers. En ajoutant use crate::front_of_house::hosting à la racine de la crate, hosting est désormais un nom valide dans cette portée, comme si le module hosting avait été défini à la racine de la crate. Les chemins amenés dans la portée avec use vérifient également la confidentialité, comme tout autre chemin.

Notez que use ne crée le raccourci que pour la portée particulière dans laquelle le use se trouve. Le listing 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 celle de l’instruction use, donc le corps de la fonction ne compilera pas.

Filename: 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();
    }
}
Listing 7-12: A use statement only applies in the scope it’s in.

L’erreur du compilateur montre que le raccourci ne s’applique plus au sein du module customer : console {{#include ../listings/ch07-managing-growing-projects/listing-07-12/output.txt}}

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of unresolved module or unlinked crate `hosting`
   |
   = help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
   |
10 +     use crate::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

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

Remarquez qu’il y a aussi un avertissement indiquant que le use n’est plus utilisé dans sa portée ! Pour corriger ce problème, déplacez le use dans le module customer également, ou faites référence au raccourci du module parent avec super::hosting dans le module enfant customer.

Créer des chemins use idiomatiques

Dans le listing 7-11, vous vous êtes peut-être demandé 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 le listing 7-13.

Filename: 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();
}
Listing 7-13: Bringing the add_to_waitlist function into scope with use, which is unidiomatic

Bien que le listing 7-11 et le listing 7-13 accomplissent la même tâche, le listing 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 lors de l’appel de la fonction. Spécifier le module parent lors de l’appel de la fonction rend clair que la fonction n’est pas définie localement tout en minimisant la répétition du chemin complet. Le code du listing 7-13 ne permet pas de savoir clairement où add_to_waitlist est défini.

D’un autre côté, lorsqu’on amène des structs, des enums et d’autres éléments avec use, il est idiomatique de spécifier le chemin complet. Le listing 7-14 montre la manière idiomatique d’amener la struct HashMap de la bibliothèque standard dans la portée d’une crate binaire.

Filename: src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
Listing 7-14: Bringing HashMap into scope in an idiomatic way

Il n’y a pas de raison forte derrière cet idiome : c’est simplement la convention qui a émergé, et les gens se sont habitués à lire et écrire du code Rust de cette manière.

L’exception à cet idiome est lorsque nous amenons deux éléments portant le même nom dans la portée avec des instructions use, car Rust ne le permet pas. Le listing 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 s’y référer.

Filename: src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}
Listing 7-15: Bringing two types with the same name into the same scope requires using their parent modules.

Comme vous pouvez le voir, utiliser les modules parents permet de distinguer les deux types Result. Si au contraire nous avions spécifié use std::fmt::Result et use std::io::Result, nous aurions deux types Result dans la même portée, et Rust ne saurait pas lequel nous voulions dire quand nous utiliserions Result.

Fournir de nouveaux noms avec le mot-clé as

Il existe une autre solution au problème d’amener deux types portant le 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. Le listing 7-16 montre une autre façon d’écrire le code du listing 7-15 en renommant l’un des deux types Result en utilisant as.

Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
Listing 7-16: Renaming a type when it’s brought into scope with the as keyword

Dans la deuxième instruction use, nous avons choisi le nouveau nom IoResult pour le type std::io::Result, qui n’entrera pas en conflit avec le Result de std::fmt que nous avons également amené dans la portée. Le listing 7-15 et le listing 7-16 sont considérés comme idiomatiques, donc le choix vous appartient !

Réexporter des noms avec pub use

Lorsque nous amenons un nom dans la portée avec le mot-clé use, le nom est privé à la portée dans laquelle nous l’avons importé. Pour permettre au code en dehors de cette portée de se référer à ce nom comme s’il avait été défini dans cette portée, nous pouvons combiner pub et use. Cette technique s’appelle la réexportation (re-exporting) parce que nous amenons un élément dans la portée mais le rendons également disponible pour que d’autres puissent l’amener dans leur portée.

Le listing 7-17 montre le code du listing 7-11 avec use dans le module racine changé en pub use.

Filename: 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();
}
Listing 7-17: Making a name available for any code to use from a new scope with 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(), ce qui aurait également nécessité que le module front_of_house soit marqué comme pub. Maintenant que ce pub use a réexporté le module hosting depuis le module racine, le code externe peut utiliser le chemin restaurant::hosting::add_to_waitlist() à la place.

La réexportation est utile lorsque la structure interne de votre code est différente de la façon dont les développeurs appelant votre code penseraient au domaine. Par exemple, dans cette métaphore du restaurant, les personnes qui gèrent le restaurant pensent en termes de « salle » et « cuisines ». Mais les clients visitant un restaurant ne penseront probablement pas aux parties du restaurant en ces termes. Avec pub use, nous pouvons écrire notre code avec une structure mais en exposer une différente. Ce faisant, notre bibliothèque est bien organisée à la fois pour les développeurs travaillant sur la bibliothèque et ceux qui l’utilisent. Nous verrons un autre exemple de pub use et comment cela affecte la documentation de votre crate dans [« Exporter une API publique pratique »][ch14-pub-use] au chapitre 14.

Utiliser des packages externes

Au chapitre 2, nous avons programmé un projet de jeu de devinettes 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 :

Filename: 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 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 listé les éléments que nous voulions amener dans la portée. Rappelons que dans [« Générer un nombre aléatoire »][rand] au chapitre 2, nous avons amené le trait Rng dans la portée et appelé la fonction rand::thread_rng : rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-03/src/main.rs:ch07-04}}

use std::io;

use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

Les membres de la communauté Rust ont rendu de nombreux packages disponibles sur crates.io, et intégrer n’importe lequel 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 les éléments de leurs crates dans la portée.

Notez que la bibliothèque standard std est également une crate externe à notre package. Comme la bibliothèque standard est livrée avec le langage Rust, nous n’avons pas besoin de modifier Cargo.toml pour inclure std. Mais nous devons nous y référer avec use pour amener les éléments dans la portée de notre package. Par exemple, pour HashMap, nous utiliserions cette ligne :

#![allow(unused)]
fn main() {
use std::collections::HashMap;
}

C’est un chemin absolu commençant par std, le nom de la crate de la bibliothèque standard.

Utiliser des chemins imbriqués pour nettoyer les listes use

Si nous utilisons plusieurs éléments définis dans la même crate ou le même module, lister chaque élément sur sa propre ligne peut prendre beaucoup d’espace vertical dans nos fichiers. Par exemple, ces deux instructions use que nous avions dans le jeu de devinettes du listing 2-4 amènent des éléments de std dans la portée :

Filename: src/main.rs
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

À la place, nous pouvons utiliser des chemins imbriqués pour amener les mêmes éléments dans la portée en une seule ligne. Nous faisons cela en spécifiant la partie commune du chemin, suivie de deux deux-points, puis des accolades autour d’une liste des parties des chemins qui diffèrent, comme montré dans le listing 7-18.

Filename: src/main.rs
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
Listing 7-18: Specifying a nested path to bring multiple items with the same prefix into scope

Dans des programmes plus grands, amener de nombreux éléments dans la portée depuis la même crate ou le même 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 d’un chemin, ce qui est utile lorsqu’on combine deux instructions use qui partagent un sous-chemin. Par exemple, le listing 7-19 montre deux instructions use : une qui amène std::io dans la portée et une qui amène std::io::Write dans la portée.

Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: Two use statements where one is a subpath of the other

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 le listing 7-20.

Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: Combining the paths in Listing 7-19 into one use statement

Cette ligne amène std::io et std::io::Write dans la portée.

Importer des éléments avec l’opérateur glob

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 glob * :

#![allow(unused)]
fn main() {
use std::collections::*;
}

Cette instruction use amène tous les éléments publics définis dans std::collections dans la portée courante. Soyez prudent lorsque vous utilisez l’opérateur glob ! 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. De plus, si la dépendance modifié ses définitions, ce que vous avez importé change également, ce qui peut entraîner des erreurs de compilation lorsque vous mettez à jour la dépendance si celle-ci ajouté par exemple une définition portant le même nom qu’une de vos définitions dans la même portée.

L’opérateur glob est souvent utilisé lors des tests pour amener tout ce qui est testé dans le module tests ; nous en parlerons dans [« Comment écrire des tests »][writing-tests] au chapitre 11. L’opérateur glob est aussi parfois utilisé dans le cadre du patron prelude : consultez la documentation de la bibliothèque standard pour plus d’informations sur ce patron.