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. Si nous voulons appeler une fonction, nous avons besoin de connaître son chemin.

Il existe deux types de chemins :

  • Un chemin absolu qui commence à partir de la racine de la crate en utilisant le nom d'une crate, ou le mot crate.
  • Un chemin relatif qui commence à partir du module courant et qui 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 ::.

Reprenons notre exemple de l'encart 7-1. Comment pouvons-nous appeler la fonction ajouter_a_la_liste_attente ? Cela revient à se demander : quel est le chemin de la fonction ajouter_a_la_liste_attente ? Dans l'encart 7-3, nous avons un peu simplifié notre code en enlevant quelques modules et quelques fonctions. 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 à la racine de la crate. La fonction manger_au_restaurant fait partie de l'API publique de notre crate de bibliothèque, donc nous la marquons avec le mot-clé pub. Dans la section ”Exposer les chemins avec le mot-clé pub, nous en apprendrons plus sur pub. Notez que cet exemple ne se compile pas pour le moment ; nous allons l'expliquer un peu plus tard.

Fichier : 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();
}

Encart 7-3 : appel à la fonction ajouter_a_la_liste_attente en utilisant un chemin absolu et relatif

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.

Après crate, 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, où 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 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. Le choix se fera en fonction 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 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 ! L'erreur que nous obtenons est affichée 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();
  |                            ^^^^^^^ 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();
   |                     ^^^^^^^ 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` due to 2 previous errors

Encart 7-4 : les erreurs de compilation du code de l'encart 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.

Les modules ne servent pas uniquement à organiser votre code. Ils définissent aussi les limites de visibilité de Rust : le code externe n'est pas autorisé à connaître, à appeler ou à se fier à des éléments internes au module. Donc, si vous voulez rendre un élément privé comme une fonction ou une structure, vous devez le placer dans un module.

La visibilité en Rust fait en sorte que tous les éléments (fonctions, méthodes, structures, énumérations, modules et constantes) sont privés par défaut. 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 la 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. Mais vous pouvez 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.

Fichier : src/lib.rs

mod salle_a_manger {
    pub 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();
}

Encart 7-5 : utiliser pub sur le module accueil permet de l'utiliser dans manger_au_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:9:37
  |
9 |     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:12:30
   |
12 |     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` due to 2 previous errors

Encart 7-6 : erreurs de compilation du code de l'encart 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.

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.

Fichier : src/lib.rs

mod salle_a_manger {
    pub mod accueil {
        pub 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();
}

Encart 7-7 : ajout du mot-clé pub devant mod accueil et fn ajouter_a_la_liste_attente pour nous permettre d'appeler la fonction à partir de manger_au_restaurant

Maintenant, le code va compiler ! Analysons les chemins relatif et absolu et vérifions pourquoi l'ajout du mot-clé pub nous permet d'utiliser ces chemins dans ajouter_a_la_liste_attente tout en respectant les règles de visibilité.

Dans le chemin absolu, nous commençons avec crate, la racine de l'arborescence de modules de notre crate. Ensuite, le module salle_a_manger est défini à la racine de la crate. Le module salle_a_manger n'est pas public, mais comme 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 !

Commencer les chemins relatifs avec super

Nous pouvons aussi créer des chemins relatifs qui commencent à partir du module parent en utilisant super au début du chemin. C'est comme débuter un chemin dans un système de fichiers avec la syntaxe ... Mais pourquoi voudrions-nous faire cela ?

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 appelle la fonction servir_commande en commençant le chemin de servir_commande avec super :

Fichier : src/lib.rs

fn servir_commande() {}

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

    fn cuisiner_commande() {}
}

Encart 7-8 : appel d'une fonction en utilisant un chemin relatif qui commence par 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 devrons ê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. 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.

Fichier : 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");
}

Encart 7-9 : une structure avec certains champs publics et d'autres privés

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 .. 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.

Fichier : 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;
}

Encart 7-10 : on rend publique une énumération et cela rend aussi toutes ses variantes publiques

Comme nous rendons 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.