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();
}
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
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();
}
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
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();
}
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() {}
}
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");
}
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;
}
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
.