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

Désigner un élément dans l’arborescence de modules

Pour indiquer à Rust où trouver un élément dans l’arborescence de modules, nous utilisons un chemin à l’instar des chemins que nous utilisons lorsque nous naviguons dans un système de fichiers. Pour appeler une fonction, nous avons besoin de connaître son chemin.

Il existe deux types de chemins :

  • Un chemin absolu est le chemin complet à partir d’une crate racine ; pour du code d’une crate externe, le chemin absolu commence par le nom de la crate, et pour le code de la crate courante, il commence avec le terme crate.
  • Un chemin relatif commence à partir du module courant et utilise self, super, ou un identificateur à l’intérieur du module.

Les chemins absolus et relatifs sont suivis par un ou plusieurs identificateurs séparés par ::.

En revenant à notre exemple de l’encart 7-1, disons que nous voulons appeler la fonction ajouter_a_la_liste_attente. Cela revient à se demander : quel est le chemin de la fonction ajouter_a_la_liste_attente ? L’encart 7-3 contient l’encart 7-1 avec quelques modules et fonctions qui ont été enlevés.

Nous allons voir deux façons d’appeler la fonction ajouter_a_la_liste_attente à partir d’une nouvelle fonction manger_au_restaurant définie dans la crate racine. Ces chemins sont corrects, mails il y a un autre problème qui empêche la compilation de ce programme tel quel. Nous allons en expliquer la raison un peu plus tard.

La fonction manger_au_restaurant fait partie de l’API publique de notre crate de bibliothèque, nous la marquons donc avec le mot-clé pub.

Filename: src/lib.rs
mod salle_a_manger {
    mod accueil {
        fn ajouter_a_la_liste_attente() {}
    }
}

pub fn manger_au_restaurant() {
    // Chemin absolu
    crate::salle_a_manger::accueil::ajouter_a_la_liste_attente();

    // Chemin relatif
    salle_a_manger::accueil::ajouter_a_la_liste_attente();
}
Listing 7-3: Calling the add_to_waitlist function using absolute and relative paths

Au premier appel de la fonction ajouter_a_la_liste_attente dans manger_au_restaurant, nous utilisons un chemin absolu. La fonction ajouter_a_la_liste_attente est définie dans la même crate que manger_au_restaurant, ce qui veut dire que nous pouvons utiliser le mot-clé crate pour démarrer un chemin absolu. Ensuite, nous ajoutons chacun des modules successifs jusqu’à ajouter_a_la_liste_attente. Nous pouvons faire l’analogie avec un système de fichiers qui aurait la même structure : nous pourrions utiliser le chemin /salle_a_manger/accueil/ajouter_a_la_liste_attente pour lancer le programme ajouter_a_la_liste_attente ; utiliser le nom crate pour partir de la racine de la crate revient à utiliser / pour partir de la racine de votre système de fichiers dans votre invite de commande.

Lors du second appel à ajouter_a_la_liste_attente dans manger_au_restaurant, nous utilisons un chemin relatif. Le chemin commence par salle_a_manger, le nom du module qui est défini au même niveau que manger_au_restaurant dans l’arborescence de modules. Ici, l’équivalent en terme de système de fichier serait le chemin salle_a_manger/accueil/ajouter_a_la_liste_attente. Commencer par un nom de module signifie que le chemin est relatif.

Choisir entre utiliser un chemin relatif ou absolu sera une décision que vous ferez en fonction de votre projet, cela dépendra de si vous êtes susceptible de déplacer la définition de l’élément souhaité séparément ou en même temps que le code qui l’utilise. Par exemple, si nous déplaçons le module salle_a_manger ainsi que la fonction manger_au_restaurant dans un module qui s’appelle experience_client, nous aurons besoin de mettre à jour le chemin absolu vers ajouter_a_la_liste_attente, mais le chemin relatif restera valide. Cependant, si nous avions déplacé uniquement la fonction manger_au_restaurant dans un module repas séparé, le chemin absolu de l’appel à ajouter_a_la_liste_attente restera le même, mais le chemin relatif aura besoin d’être mis à jour. Notre préférence est généralement d’utiliser un chemin absolu car il est plus facile de déplacer les définitions de code et les appels aux éléments indépendamment les uns des autres.

Essayons de compiler l’encart 7-3 et essayons de comprendre pourquoi il ne se compile pas pour le moment ! Les erreurs que nous obtenons sont affichées dans l’encart 7-4.

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

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

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

Le message d’erreur nous rappelle que ce module accueil est privé. Autrement dit, nous avons des chemins corrects pour le module accueil et pour la fonction ajouter_a_la_liste_attente, mais Rust ne nous laisse pas les utiliser car il n’a pas accès aux sections privées. En Rust, tous les éléments (fonctions, méthodes, structures, énumérations, modules et constantes) sont privés pour leur module parent par défaut. Si vous voulez rendre privé un élément comme une fonction ou une structure, il vous suffit de le placer dans un module.

Les éléments dans un module parent ne peuvent pas utiliser les éléments privés dans les modules enfants, mais les éléments dans les modules enfants peuvent utiliser les éléments dans les modules parents. C’est parce que les modules enfants englobent et cachent les détails de leur implémentation, mais les modules enfants peuvent voir dans quel contexte ils sont définis. Pour continuer notre métaphore du restaurant, considérez que les règles de visibilité de Rust fonctionnent comme les cuisines d’un restaurant : ce qui s’y passe n’est pas connu des clients, mais les gestionnaires peuvent tout voir et tout faire dans le restaurant dans lequel ils travaillent.

Rust a décidé de faire fonctionner le système de modules de façon à ce que les détails d’implémentation interne sont cachés par défaut. Ainsi, vous savez quelles parties du code interne vous pouvez changer sans casser le code externe. Cependant, Rust vous permet d’avoir l’option d’exposer aux parents des parties internes des modules enfants en utilisant le mot-clé pub afin de les rendre publiques.

Exposer des chemins avec le mot-clé pub

Retournons à l’erreur de l’encart 7-4 qui nous informe que le module accueil est privé. Nous voulons que la fonction manger_au_restaurant du module parent ait accès à la fonction ajouter_a_la_liste_attente du module enfant, donc nous utilisons le mot-clé pub sur le module accueil, comme dans l’encart 7-5.

Filename: src/lib.rs
mod salle_a_manger {
    pub mod accueil {
        fn ajouter_a_la_liste_attente() {}
    }
}

// -- partie masquée ici --
pub fn manger_au_restaurant() {
    // Chemin absolu
    crate::salle_a_manger::accueil::ajouter_a_la_liste_attente();

    // Chemin relatif
    salle_a_manger::accueil::ajouter_a_la_liste_attente();
}
Listing 7-5: Declaring the hosting module as pub to use it from eat_at_restaurant

Malheureusement, il reste une erreur dans le code de l’encart 7-5, la voici dans l’encart 7-6.

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

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

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 accueil rend public le module. Avec cette modification, si nous pouvons accéder à salle_a_manger, alors nous pouvons accéder à accueil. Mais le contenu de accueil reste privé ; rendre le module public ne rend pas son contenu public. Le mot-clé pub sur un module permet uniquement au code de ses parents d’y faire référence, pas d’accéder à son code interne. Comme les modules sont des conteneurs, le simple fait de rendre le module public ne suffit pas ; il faut aller plus loin et aussi choisir de rendre publics un ou plusieurs de ces éléments.

Les erreurs dans l’encart 7-6 nous informent que la fonction ajouter_a_la_liste_attente est privée. Les règles de visibilité s’appliquent aussi bien aux modules qu’aux structures, énumérations, fonctions et méthodes.

Rendons publique la fonction ajouter_a_la_liste_attente, en ajoutant le mot-clé pub devant sa définition, comme dans l’encart 7-7.

Filename: src/lib.rs
mod salle_a_manger {
    pub mod accueil {
        pub fn ajouter_a_la_liste_attente() {}
    }
}

// -- partie masquée ici --
pub fn manger_au_restaurant() {
    // Chemin absolu
    crate::salle_a_manger::accueil::ajouter_a_la_liste_attente();

    // Chemin relatif
    salle_a_manger::accueil::ajouter_a_la_liste_attente();
}
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 voir pourquoi l’ajout du mot-clé pub nous permet d’utiliser ces chemins dans manger_au_restaurant tout en respectant les règles de visibilité, analysons les chemins relatif et absolu.

Dans le chemin absolu, nous commençons avec crate, la racine de l’arborescence de modules de notre crate. Le module salle_a_manger est défini à la racine de la crate. Alors que le module salle_a_manger n’est pas public, car la fonction manger_au_restaurant est définie dans le même module que salle_a_manger (car manger_au_restaurant et salle_a_manger sont frères), nous pouvons utiliser salle_a_manger à partir de manger_au_restaurant. Ensuite, nous avons le module accueil, défini avec pub. Nous pouvons accéder au module parent de accueil, donc nous pouvons accéder à accueil. Enfin, la fonction ajouter_a_la_liste_attente est elle aussi définie avec pub et nous pouvons accéder à son module parent, donc au final cet appel à la fonction fonctionne bien !

Dans le chemin relatif, le fonctionnement est le même que le chemin absolu sauf pour la première étape : plutôt que de démarrer de la racine de la crate, le chemin commence à partir de salle_a_manger. Le module salle_a_manger est défini dans le même module que manger_au_restaurant, donc le chemin relatif qui commence à partir du module où est défini manger_au_restaurant fonctionne bien. Ensuite, comme accueil et ajouter_a_la_liste_attente sont définis avec pub, le reste du chemin fonctionne, et cet appel à la fonction est donc valide !

Si vous envisagez de partager votre crate de bibliothèque afin que d’autres projets puissent utiliser votre code, votre API publique constitue le contrat que vous concluez avec les utilisateurs de votre crate, contrat qui définit la manière dont ces utilisateurs peuvent interagir avec votre code. Il y a de nombreux aspects à prendre en compte en ce qui concerne la gestion des changements de votre API publique, afin de faciliter l’utilisation de votre crate. Ces aspects dépassent le cadre de cet ouvrage ; si ce sujet vous intéresse, consultez les directives relatives aux API Rust.

Bonnes pratiques pour les paquets comprenant un binaire et une bibliothèque

Nous avons mentionné le fait qu’un paquet peu contenir à la fois une crate binaire src/main.rs et une crate bibliothèque src/lib.rs, et que ces deux crates auront par défaut le nom du paquet. Généralement, les paquets qui suivent ce modèle, contenant à la fois une crate de bibliothèque et une crate binaire, ne contiennent dans la crate binaire que le code nécessaire pour démarrer un exécutable qui appelle le code défini dans la crate de bibliothèque. Ceci permet aux autres projets de bénéficier du maximum de fonctionnalités fournies par le paquet car le code de la crate de bibliothèque peut être partagé.

L’arborescence des modules doit être définie dans src/lib.rs. Ainsi, tout élément public peut être utilisé dans la crate binaire en commençant les chemins avec le nom du paquet. La crate binaire devient un utilisateur de la bibliothèque tout comme une crate complètement extérieure le ferait : elle ne peut utiliser que l’API publique. Ceci aide à mettre au point une bonne interface de programmation (API) ; non seulement vous êtes l’auteur, mais vous êtes également un client !

Dans le chapitre 12, nous montrerons cette pratique organisationnelle avec un programme en ligne de commande qui contiendra en même temps une crate binaire et une crate de bibliothèque.

Commencer les chemins relatifs avec super

Nous pouvons créer des chemins relatifs qui commencent à partir du module parent, au lieu du module courant ou de la crate racine, en utilisant super au début du chemin. C’est comme débuter un chemin dans un système de fichiers avec la syntaxe .., qui désigne le répertoire parent. Utiliser super nous permet de faire référence à un élément dont nous savons qu’il est situé dans le module parent, ce qui peut faciliter la réorganisation de l’arborescence de modules quand le module est étroitement lié au parent, mais que le parent peut éventuellement être déplacé ailleurs dans l’arborescence des modules.

Imaginons le code dans l’encart 7-8 qui représente le cas où le chef corrige une commande erronée et l’apporte personnellement au client pour s’excuser. La fonction corriger_commande_erronee définie dans le module cuisine appelle la fonction servir_commande définie dans le module parent en spécifiant le chemin vers servir_commande, en commençant par super.

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

mod cuisines {
    fn corriger_commande_erronee() {
        cuisiner_commande();
        super::servir_commande();
    }

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

La fonction corriger_commande_erronee est dans le module cuisines, donc nous pouvons utiliser super pour nous rendre au module parent de cuisines, qui dans notre cas est crate, la racine. De là, nous cherchons servir_commande et nous la trouvons. Avec succès ! Nous pensons que le module cuisines et la fonction servir_commande vont toujours garder la même relation et devront être déplacés ensemble si nous réorganisons l’arborescence de modules de la crate. Ainsi, nous avons utilisé super pour avoir moins de code à mettre à jour à l’avenir si ce code est déplacé dans un module différent.

Rendre publiques des structures et des énumérations

Nous pouvons aussi utiliser pub pour déclarer des structures et des énumérations publiquement, mais il y a d’autres points à prendre en compte concernant l’utilisation de pub avec des structures et des énumérations. Si nous utilisons pub avant la définition d’une structure, nous rendons la structure publique, mais les champs de la structure restent privés. Nous pouvons rendre chaque champ public ou non au cas par cas. Dans l’encart 7-9, nous avons défini une structure publique cuisines::PetitDejeuner avec un champ public tartine_grillee mais avec un champ privé fruit_de_saison. Cela simule un restaurant où le client peut choisir le type de pain qui accompagne le repas, mais le chef décide des fruits qui accompagnent le repas en fonction de la saison et ce qu’il y a en stock. Les fruits disponibles changent rapidement, donc les clients ne peuvent pas choisir le fruit ou même voir quel fruit ils obtiendront.

Filename: src/lib.rs
mod cuisines {
    pub struct PetitDejeuner {
        pub tartine_grillee: String,
        fruit_de_saison: String,
    }

    impl PetitDejeuner {
        pub fn en_ete(tartine_grillee: &str) -> PetitDejeuner {
            PetitDejeuner {
                tartine_grillee: String::from(tartine_grillee),
                fruit_de_saison: String::from("pêches"),
            }
        }
    }
}

pub fn manger_au_restaurant() {
    // On commande un petit-déjeuner en été avec tartine grillée au seigle.
    let mut repas = cuisines::PetitDejeuner::en_ete("seigle");
    // On change d'avis sur le pain que nous souhaitons.
    repas.tartine_grillee = String::from("blé");
    println!( "Je voudrais une tartine grillée au {}, s'il vous plaît.",
              repas.tartine_grillee);

    // La prochaine ligne ne va pas se compiler si nous ne la commentons pas,
    // car nous ne sommes pas autorisés à voir ou modifier le fruit de saison
    // qui accompagne le repas.
    // repas.fruit_de_saison = String::from("myrtilles");
}
Listing 7-9: A struct with some public fields and some private fields

Comme le champ tartine_grillee est public dans la structure cuisines::PetitDejeuner, nous pouvons lire et écrire dans le champ tartine_grillee à partir de manger_au_restaurant en utilisant la notation .. Notez aussi que nous ne pouvons pas utiliser le champ fruit_de_saison dans manger_au_restaurant car fruit_de_saison est privé. Essayez de dé-commenter la ligne qui tente de modifier la valeur du champ fruit_de_saison et voyez l’erreur que vous obtenez !

Aussi, remarquez que comme cuisines::PetitDejeuner a un champ privé, la structure a besoin de fournir une fonction associée publique qui construit une instance de PetitDejeuner (que nous avons nommée en_ete ici). Si PetitDejeuner n’avait pas une fonction comme celle-ci, nous ne pourrions pas créer une instance de PetitDejeuner dans manger_au_restaurant car nous ne pourrions pas donner une valeur au champ privé fruit_de_saison dans manger_au_restaurant.

Par contre, si nous rendons publique une énumération, toutes ses variantes seront publiques. Nous avons simplement besoin d’un pub devant le mot-clé enum, comme dans l’encart 7-10.

Filename: src/lib.rs
mod cuisines {
    pub enum AmuseBouche {
        Soupe,
        Salade,
    }
}

pub fn manger_au_restaurant() {
    let commande1 = cuisines::AmuseBouche::Soupe;
    let commande2 = cuisines::AmuseBouche::Salade;
}
Listing 7-10: Designating an enum as public makes all its variants public.

Comme nous avons rendu l’énumération AmuseBouche publique, nous pouvons utiliser les variantes Soupe et Salade dans manger_au_restaurant.

Les énumérations ne sont pas très utiles si elles n’ont pas leurs variantes publiques ; et cela serait pénible d’avoir à marquer toutes les variantes de l’énumération avec pub, donc par défaut les variantes d’énumérations sont publiques. Les structures sont souvent utiles sans avoir de champs publics, donc les champs des structures sont tous privés par défaut, sauf si ces éléments sont marqués d’un pub.

Il y a encore une chose que nous n’avons pas abordée concernant pub, et c’est la dernière fonctionnalité du système de modules : le mot-clé use. Nous commencerons par parler de l’utilisation de use de manière générale, puis nous verrons comment combiner pub et use.