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

Les chemins pour désigner un élément dans l’arborescence des modules

Pour indiquer à Rust où trouver un élément dans un arbre de modules, nous utilisons un chemin de la même manière que nous utilisons un chemin pour naviguer dans un système de fichiers. Pour appeler une fonction, nous devons connaître son chemin.

Un chemin peut prendre deux formes :

  • Un chemin absolu est le chemin complet à partir de la racine d’une crate ; pour du code provenant d’une crate externe, le chemin absolu commence par le nom de la crate, et pour du code provenant de la crate courante, il commence par le mot littéral crate.
  • Un chemin relatif commence à partir du module courant et utilise self, super ou un identifiant dans le module courant.

Les chemins absolus et relatifs sont suivis d’un où plusieurs identifiants séparés par des doubles deux-points (::).

Revenons au listing 7-1, et supposons que nous voulions appeler la fonction add_to_waitlist. C’est comme demander : quel est le chemin de la fonction add_to_waitlist ? Le listing 7-3 contient le listing 7-1 avec certains modules et fonctions supprimés.

Nous montrerons deux façons d’appeler la fonction add_to_waitlist depuis une nouvelle fonction, eat_at_restaurant, définie à la racine de la crate. Ces chemins sont corrects, mais il reste un autre problème qui empêchera cet exemple de compiler en l’état. Nous expliquerons pourquoi dans un instant.

La fonction eat_at_restaurant fait partie de l’API publique de notre crate de bibliothèque, nous la marquons donc avec le mot-clé pub. Dans la section [« Exposer les chemins avec le mot-clé pub »][pub], nous entrerons plus en détail sur pub.

Filename: src/lib.rs
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-3: Calling the add_to_waitlist function using absolute and relative paths

La première fois que nous appelons la fonction add_to_waitlist dans eat_at_restaurant, nous utilisons un chemin absolu. La fonction add_to_waitlist est définie dans la même crate que eat_at_restaurant, ce qui signifie que nous pouvons utiliser le mot-clé crate pour commencer un chemin absolu. Nous incluons ensuite chacun des modules successifs jusqu’à atteindre add_to_waitlist. Vous pouvez imaginer un système de fichiers avec la même structure : nous spécifierions le chemin /front_of_house/hosting/add_to_waitlist pour exécuter le programme add_to_waitlist ; utiliser le nom crate pour partir de la racine de la crate revient à utiliser / pour partir de la racine du système de fichiers dans votre terminal.

La deuxième fois que nous appelons add_to_waitlist dans eat_at_restaurant, nous utilisons un chemin relatif. Le chemin commence par front_of_house, le nom du module défini au même niveau de l’arbre de modules que eat_at_restaurant. Ici, l’équivalent dans le système de fichiers serait d’utiliser le chemin front_of_house/hosting/add_to_waitlist. Commencer par un nom de module signifie que le chemin est relatif.

Choisir d’utiliser un chemin relatif ou absolu est une décision que vous prendrez en fonction de votre projet, et cela dépend de la probabilité que vous déplaciez le code de définition de l’élément séparément ou ensemble avec le code qui utilise l’élément. Par exemple, si nous déplacions le module front_of_house et la fonction eat_at_restaurant dans un module nommé customer_experience, nous devrions mettre à jour le chemin absolu vers add_to_waitlist, mais le chemin relatif serait toujours valide. En revanche, si nous déplacions la fonction eat_at_restaurant séparément dans un module nommé dining, le chemin absolu vers l’appel add_to_waitlist resterait le même, mais le chemin relatif devrait être mis à jour. Notre préférence en général est de spécifier des chemins absolus, car il est plus probable que nous voudrons déplacer les définitions de code et les appels d’éléments indépendamment les uns des autres.

Essayons de compiler le listing 7-3 et découvrons pourquoi il ne compilé pas encore ! Les erreurs que nous obtenons sont présentées dans le listing 7-4.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
  |                            |
  |                            private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
   |                     |
   |                     private module
   |
note: the module `hosting` is defined here
  --> src/lib.rs:2:5
   |
 2 |     mod hosting {
   |     ^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Listing 7-4: Compiler errors from building the code in Listing 7-3

Les messages d’erreur indiquent que le module hosting est privé. En d’autres termes, nous avons les chemins corrects pour le module hosting et la fonction add_to_waitlist, mais Rust ne nous permet pas de les utiliser car il n’a pas accès aux sections privées. En Rust, tous les éléments (fonctions, méthodes, structs, enums, modules et constantes) sont privés par rapport aux modules parents par défaut. Si vous voulez rendre un élément comme une fonction ou une struct privé, vous le placez dans un module.

Les éléments d’un module parent ne peuvent pas utiliser les éléments privés à l’intérieur des modules enfants, mais les éléments des modules enfants peuvent utiliser les éléments de leurs modules ancêtres. C’est parce que les modules enfants enveloppent et cachent leurs détails d’implémentation, mais les modules enfants peuvent voir le contexte dans lequel ils sont définis. Pour continuer avec notre métaphore, pensez aux règles de confidentialité comme au bureau de direction d’un restaurant : ce qui s’y passe est privé pour les clients du restaurant, mais les responsables peuvent voir et faire tout dans le restaurant qu’ils gèrent.

Rust a choisi de faire fonctionner le système de modules de cette manière afin que masquer les détails d’implémentation internes soit le comportement par défaut. De cette façon, vous savez quelles parties du code interne vous pouvez modifier sans casser le code externe. Cependant, Rust vous donne la possibilité d’exposer les parties internes du code des modules enfants aux modules ancêtres externes en utilisant le mot-clé pub pour rendre un élément public.

Exposer les chemins avec le mot-clé pub

Revenons à l’erreur du listing 7-4 qui nous indiquait que le module hosting est privé. Nous voulons que la fonction eat_at_restaurant dans le module parent ait accès à la fonction add_to_waitlist dans le module enfant, donc nous marquons le module hosting avec le mot-clé pub, comme montré dans le listing 7-5.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

// -- snip --
pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-5: Declaring the hosting module as pub to use it from eat_at_restaurant

Malheureusement, le code du listing 7-5 produit toujours des erreurs de compilation, comme montré dans le listing 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:10:37
   |
10 |     crate::front_of_house::hosting::add_to_waitlist();
   |                                     ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
 3 |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:13:30
   |
13 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
 3 |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Listing 7-6: Compiler errors from building the code in Listing 7-5

Que s’est-il passé ? Ajouter le mot-clé pub devant mod hosting rend le module public. Avec ce changement, si nous pouvons accéder à front_of_house, nous pouvons accéder à hosting. Mais le contenu de hosting est toujours privé ; rendre le module public ne rend pas son contenu public. Le mot-clé pub sur un module permet uniquement au code de ses modules ancêtres de s’y référer, pas d’accéder à son code interne. Puisque les modules sont des conteneurs, nous ne pouvons pas faire grand-chose en rendant uniquement le module public ; nous devons aller plus loin et choisir de rendre un où plusieurs des éléments du module publics également.

Les erreurs du listing 7-6 indiquent que la fonction add_to_waitlist est privée. Les règles de confidentialité s’appliquent aux structs, enums, fonctions et méthodes ainsi qu’aux modules.

Rendons également la fonction add_to_waitlist publique en ajoutant le mot-clé pub avant sa définition, comme dans le listing 7-7.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// -- snip --
pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-7: Adding the pub keyword to mod hosting and fn add_to_waitlist lets us call the function from eat_at_restaurant.

Maintenant le code va compiler ! Pour comprendre pourquoi l’ajout du mot-clé pub nous permet d’utiliser ces chemins dans eat_at_restaurant conformément aux règles de confidentialité, examinons les chemins absolus et relatifs.

Dans le chemin absolu, nous commençons par crate, la racine de l’arbre de modules de notre crate. Le module front_of_house est défini à la racine de la crate. Bien que front_of_house ne soit pas public, comme la fonction eat_at_restaurant est définie dans le même module que front_of_house (c’est-à-dire que eat_at_restaurant et front_of_house sont frères), nous pouvons faire référence à front_of_house depuis eat_at_restaurant. Ensuite vient le module hosting marqué avec pub. Nous pouvons accéder au module parent de hosting, donc nous pouvons accéder à hosting. Enfin, la fonction add_to_waitlist est marquée avec pub, et nous pouvons accéder à son module parent, donc cet appel de fonction fonctionne !

Dans le chemin relatif, la logique est la même que pour le chemin absolu sauf pour la première étape : plutôt que de commencer à la racine de la crate, le chemin commence à partir de front_of_house. Le module front_of_house est défini dans le même module que eat_at_restaurant, donc le chemin relatif commençant depuis le module dans lequel eat_at_restaurant est défini fonctionne. Ensuite, puisque hosting et add_to_waitlist sont marqués avec pub, le reste du chemin fonctionne, et cet appel de fonction est valide !

Si vous prévoyez de partager votre crate de bibliothèque pour que d’autres projets puissent utiliser votre code, votre API publique est votre contrat avec les utilisateurs de votre crate qui détermine comment ils peuvent interagir avec votre code. Il y a de nombreuses considérations autour de la gestion des modifications de votre API publique pour faciliter la dépendance des gens envers votre crate. Ces considérations dépassent le cadre de ce livre ; si vous êtes intéressé par ce sujet, consultez [les directives de l’API Rust][api-guidelines].

Bonnes pratiques pour les packages avec un binaire et une bibliothèque

Nous avons mentionné qu’un package peut contenir à la fois une racine de crate binaire src/main.rs et une racine de crate de bibliothèque src/lib.rs, et les deux crates porteront le nom du package par défaut. Typiquement, les packages suivant ce schéma de contenir à la fois une crate de bibliothèque et une crate binaire auront juste assez de code dans la crate binaire pour démarrer un exécutable qui appelle du code défini dans la crate de bibliothèque. Cela permet à d’autres projets de bénéficier du maximum de fonctionnalités que le package fournit, car le code de la crate de bibliothèque peut être partagé.

L’arbre de modules devrait être défini dans src/lib.rs. Ensuite, tous les éléments publics peuvent être utilisés dans la crate binaire en commençant les chemins par le nom du package. La crate binaire devient un utilisateur de la crate de bibliothèque, tout comme une crate complètement externe utiliserait la crate de bibliothèque : elle ne peut utiliser que l’API publique. Cela vous aide à concevoir une bonne API ; vous n’êtes pas seulement l’auteur, mais aussi un client !

Au chapitre 12, nous démontrerons cette pratique d’organisation avec un programme en ligne de commande qui contiendra à la fois une crate binaire et une crate de bibliothèque.

Commencer les chemins relatifs avec super

Nous pouvons construire des chemins relatifs qui commencent dans le module parent, plutôt que dans le module courant ou la racine de la crate, en utilisant super au début du chemin. C’est comme commencer un chemin de système de fichiers avec la syntaxe .. qui signifie aller au répertoire parent. Utiliser super nous permet de faire référence à un élément que nous savons être dans le module parent, ce qui peut faciliter la réorganisation de l’arbre de modules lorsque le module est étroitement lié au parent mais que le parent pourrait être déplacé ailleurs dans l’arbre de modules un jour.

Considérons le code du listing 7-8 qui modélise la situation dans laquelle un chef corrige une commande incorrecte et l’apporte personnellement au client. La fonction fix_incorrect_order définie dans le module back_of_house appelle la fonction deliver_order définie dans le module parent en spécifiant le chemin vers deliver_order, en commençant par super.

Filename: src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
Listing 7-8: Calling a function using a relative path starting with super

La fonction fix_incorrect_order se trouve dans le module back_of_house, donc nous pouvons utiliser super pour aller au module parent de back_of_house, qui dans ce cas est crate, la racine. De là, nous cherchons deliver_order et le trouvons. Succès ! Nous pensons que le module back_of_house et la fonction deliver_order sont susceptibles de rester dans la même relation l’un avec l’autre et d’être déplacés ensemble si nous décidons de réorganiser l’arbre de modules de la crate. Par conséquent, nous avons utilisé super afin d’avoir moins d’endroits où mettre à jour le code à l’avenir si ce code est déplacé vers un autre module.

Rendre les structs et les enums publics

Nous pouvons également utiliser pub pour désigner les structs et les enums comme publics, mais il y à quelques détails supplémentaires concernant l’utilisation de pub avec les structs et les enums. Si nous utilisons pub avant une définition de struct, nous rendons la struct publique, mais les champs de la struct resteront privés. Nous pouvons rendre chaque champ public ou non au cas par cas. Dans le listing 7-9, nous avons défini une struct publique back_of_house::Breakfast avec un champ public toast mais un champ privé seasonal_fruit. Cela modélise le cas d’un restaurant où le client peut choisir le type de pain qui accompagne un repas, mais le chef décide quel fruit accompagne le repas en fonction de ce qui est de saison et en stock. Les fruits disponibles changent rapidement, donc les clients ne peuvent pas choisir le fruit ni même voir quel fruit ils recevront.

Filename: src/lib.rs
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // Order a breakfast in the summer with Rye toast.
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like.
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // The next line won't compilé if we uncomment it; we're not allowed
    // to see or modify the seasonal fruit that comes with the meal.
    // meal.seasonal_fruit = String::from("blueberries");
}
Listing 7-9: A struct with some public fields and some private fields

Puisque le champ toast de la struct back_of_house::Breakfast est public, dans eat_at_restaurant nous pouvons écrire et lire le champ toast en utilisant la notation avec point. Remarquez que nous ne pouvons pas utiliser le champ seasonal_fruit dans eat_at_restaurant, car seasonal_fruit est privé. Essayez de décommenter la ligne modifiant la valeur du champ seasonal_fruit pour voir quelle erreur vous obtenez !

Notez également que, puisque back_of_house::Breakfast à un champ privé, la struct doit fournir une fonction associée publique qui construit une instance de Breakfast (nous l’avons nommée summer ici). Si Breakfast n’avait pas une telle fonction, nous ne pourrions pas créer une instance de Breakfast dans eat_at_restaurant, car nous ne pourrions pas définir la valeur du champ privé seasonal_fruit dans eat_at_restaurant.

En revanche, si nous rendons une enum publique, tous ses variants sont alors publics. Nous n’avons besoin du pub que devant le mot-clé enum, comme montré dans le listing 7-10.

Filename: src/lib.rs
mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}
Listing 7-10: Designating an enum as public makes all its variants public.

Puisque nous avons rendu l’enum Appetizer publique, nous pouvons utiliser les variants Soup et Salad dans eat_at_restaurant.

Les enums ne sont pas très utiles à moins que leurs variants soient publics ; il serait pénible de devoir annoter tous les variants d’enum avec pub dans chaque cas, donc le comportement par défaut pour les variants d’enum est d’être publics. Les structs sont souvent utiles sans que leurs champs soient publics, donc les champs de struct suivent la règle générale selon laquelle tout est privé par défaut sauf annotation avec pub.

Il y à une dernière situation impliquant pub que nous n’avons pas couverte, et c’est notre dernière fonctionnalité du système de modules : le mot-clé use. Nous couvrirons d’abord use seul, puis nous montrerons comment combiner pub et use.