Mit Pfaden auf ein Element im Modulbaum verweisen
Um Rust zu zeigen, wo ein Element in einem Modulbaum zu finden ist, verwenden wir einen Pfad, auf gleiche Weise wie beim Navigieren durch ein Dateisystem. Um eine Funktion aufzurufen, müssen wir ihren Pfad kennen.
Ein Pfad kann zwei Formen annehmen:
- Ein absoluter Pfad ist der vollständige Pfad ausgehend von einer
Kistenwurzel; für Code aus einer externen Kiste beginnt der absolute Pfad mit
der Kistenwurzel, und für Code aus der aktuellen Kiste beginnt er mit dem
Literal
crate
. - Ein relativer Pfad startet beim aktuellen Modul und benutzt
self
,super
oder einen Bezeichner im aktuellen Modul.
Sowohl absolute als auch relative Pfade bestehen aus einem oder mehreren
Bezeichnern, die durch doppelte Doppelpunkte (::
) getrennt sind.
Um zu Codeblock 7-1 zurückzukehren, nehmen wir an, wir wollen die Funktion
add_to_waitlist
aufrufen. Das ist dasselbe wie die Frage, wie der Pfad der
Funktion add_to_waitlist
ist. Codeblock 7-3 enthält Codeblock 7-1, wobei
einige Module und Funktionen entfernt wurden.
Wir zeigen zwei Möglichkeiten, wie die Funktion add_to_waitlist
von einer
neuen Funktion eat_at_restaurant
aus aufgerufen werden kann, die in der
Kistenwurzel definiert ist. Diese Pfade sind korrekt, aber es gibt noch ein
weiteres Problem, das verhindert, dass dieses Beispiel in dieser Form
kompiliert. Wir werden gleich erklären, warum.
Die Funktion eat_at_restaurant
ist Teil der öffentlichen
Programmierschnittstelle (API) unserer Bibliothekskiste, daher markieren wir
sie mit dem Schlüsselwort pub
. Im Abschnitt „Pfade mit dem Schlüsselwort
pub
öffnen“ gehen wir näher auf pub
ein.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { mod front_of_house { mod hosting { fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absoluter Pfad crate::front_of_house::hosting::add_to_waitlist(); // Relativer Pfad front_of_house::hosting::add_to_waitlist(); } }
Beim ersten Aufruf der Funktion add_to_waitlist
in eat_at_restaurant
verwenden wir einen absoluten Pfad. Die Funktion add_to_waitlist
ist in der
gleichen Kiste definiert wie eat_at_restaurant
, daher können wir das
Schlüsselwort crate
verwenden, um einen absoluten Pfad zu beginnen. Dann
geben wir jedes der aufeinanderfolgenden Module an, bis wir add_to_waitlist
erreichen. Du kannst dir ein Dateisystem mit der gleichen Struktur vorstellen:
Wir würden den Pfad /front_of_house/hosting/add_to_waitlist
angeben, um das
Programm add_to_waitlist
auszuführen; das Verwenden des Namens crate
, um
von der Kistenwurzel aus zu beginnen, ist analog zu /
, um vom
Dateisystem-Wurzelverzeichnis in deinem Terminal aus zu beginnen.
Beim zweiten Aufruf von add_to_waitlist
in eat_at_restaurant
verwenden wir
einen relativen Pfad. Der Pfad beginnt mit front_of_house
, dem Namen des
Moduls, das auf der gleichen Ebene des Modulbaums definiert ist wie
eat_at_restaurant
. Hier wäre das Dateisystem-Äquivalent die Verwendung des
Pfades front_of_house/hosting/add_to_waitlist
. Mit einem Modulnamen zu
beginnen bedeutet, dass der Pfad relativ ist.
Die Überlegung, ob ein relativer oder absoluter Pfad verwendet wird, ist eine
Entscheidung, die du auf Basis deines Projekts treffen wirst, und hängt davon
ab, ob du den Code für die Elementdefinition eher separat oder zusammen mit dem
Code ablegen möchtest, der das Element verwendet. Wenn wir zum Beispiel das
Modul front_of_house
und die Funktion eat_at_restaurant
in ein Modul namens
customer_experience
verschieben, müssten wir den absoluten Pfad in
add_to_waitlist
ändern, aber der relative Pfad wäre immer noch gültig. Wenn
wir jedoch die Funktion eat_at_restaurant
in ein separates Modul namens
dining
verschieben würden, würde der absolute Pfad beim Aufruf
add_to_waitlist
gleich bleiben, aber der relative Pfad müsste aktualisiert
werden. Wir bevorzugen generell die Angabe absoluter Pfade, da es
wahrscheinlicher ist, dass Codedefinitionen und Elementaufrufe unabhängig
voneinander verschoben werden.
Lass uns versuchen, Codeblock 7-3 zu kompilieren, und herausfinden, warum er sich noch nicht kompilieren lässt! Die Fehler, die wir erhalten, sind in Codeblock 7-4 zu sehen.
$ 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
Die Fehlermeldungen besagen, dass das Modul hosting
privat ist. Mit anderen
Worten, wir haben die korrekten Pfade für das Modul hosting
und die Funktion
add_to_waitlist
angegeben, aber Rust lässt sie uns nicht nutzen, weil es
keinen Zugriff auf die privaten Abschnitte hat. In Rust sind alle Elemente
(Funktionen, Methoden, Strukturen, Aufzählungen, Module und Konstanten)
standardmäßig privat für übergeordnete Module. Wenn du ein Element wie eine
Funktion oder Struktur privat machen willst, setze es in ein Modul.
Objekte in einem übergeordneten Modul können die privaten Objekte in untergeordneten Modulen nicht verwenden, aber Objekte in untergeordneten Modulen können die Objekte in ihren übergeordneten Modulen verwenden. Der Grund dafür ist, dass untergeordnete Module ihre Implementierungsdetails ein- und ausblenden, aber die untergeordneten Module können den Gültigkeitsbereich sehen, in dem sie definiert sind. Um mit unserer Metapher fortzufahren, stelle dir die Datenschutzregeln wie das Backoffice eines Restaurants vor: Was dort drinnen passiert, ist für Restaurantkunden privat, aber Büroleiter können alles im Restaurant, in dem sie arbeiten, sehen und tun.
Rust entschied sich dafür, das Modulsystem auf diese Weise funktionieren zu
lassen, sodass das Ausblenden innerer Implementierungsdetails die Vorgabe ist.
Auf diese Weise weißt du, welche Teile des inneren Codes du ändern kannst, ohne
den äußeren Code zu brechen. Rust gibt dir jedoch die Möglichkeit, innere Teile
des Codes von Kindmodulen für äußere Vorgängermodule offenzulegen, indem du das
Schlüsselwort pub
verwendest, um ein Element öffentlich zu machen.
Pfade mit dem Schlüsselwort pub
öffentlich machen
Kehren wir zum Fehler in Codeblock 7-4 zurück, der uns sagte, das Modul
hosting
sei privat. Wir wollen, dass die Funktion eat_at_restaurant
im
übergeordneten Modul Zugriff auf die Funktion add_to_waitlist
im
untergeordneten Modul hat, also markieren wir das Modul hosting
mit dem
Schlüsselwort pub
, wie in Codeblock 7-5 gezeigt.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { mod front_of_house { pub mod hosting { fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absoluter Pfad crate::front_of_house::hosting::add_to_waitlist(); // Relativer Pfad front_of_house::hosting::add_to_waitlist(); } }
Leider führt der Code in Codeblock 7-5 immer noch zu Kompilierfehlern, wie Codeblock 7-6 zeigt.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | 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:12:30
|
12 | 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
Was ist passiert? Das Hinzufügen des Schlüsselworts pub
vor mod hosting
macht das Modul öffentlich. Wenn wir auf front_of_house
zugreifen können,
können wir mit dieser Änderung auch auf hosting
zugreifen. Aber die Inhalte
von hosting
sind immer noch privat; das Modul öffentlich zu machen, macht
seinen Inhalt nicht öffentlich. Das Schlüsselwort pub
für ein Modul erlaubt
es dem Code in seinen Vorgängermodulen nur, auf das Modul zu referenzieren,
nicht aber auf seinen inneren Code zuzugreifen. Da Module Container sind,
können wir nicht viel tun, indem wir nur das Modul öffentlich machen; wir
müssen weiter gehen und eines oder mehrere der Elemente innerhalb des Moduls
ebenfalls öffentlich machen.
Die Fehler in Codeblock 7-6 besagen, dass die Funktion add_to_waitlist
privat
ist. Die Datenschutzregeln gelten für Strukturen, Aufzählungen, Funktionen und
Methoden sowie für Module.
Lass uns auch die Funktion add_to_waitlist
öffentlich machen, indem wir das
Schlüsselwort pub
vor ihre Definition hinzufügen, wie in Codeblock 7-7.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absoluter Pfad
crate::front_of_house::hosting::add_to_waitlist();
// Relativer Pfad
front_of_house::hosting::add_to_waitlist();
}
Jetzt kompiliert der Code! Um zu sehen, warum das Hinzufügen des Schlüsselworts
pub
uns erlaubt, diese Pfade in eat_at_restaurant
im Hinblick auf die
Datenschutzregeln zu verwenden, sehen wir uns die absoluten und relativen Pfade
an.
Auf dem absoluten Pfad beginnen wir mit crate
, der Wurzel des Modulbaums
unserer Kiste. Dann wird das Modul front_of_house
in der Kistenwurzel
definiert. Während das Modul front_of_house
nicht öffentlich ist, weil die
eat_at_restaurant
-Funktion im gleichen Modul wie front_of_house
definiert
ist (d.h. eat_at_restaurant
und front_of_house
sind Geschwister), können
wir auf front_of_house
von eat_at_restaurant
aus zugreifen. Als nächstes
wird das Modul hosting
mit pub
gekennzeichnet. Wir können auf das
übergeordnete Modul von hosting
zugreifen, also können wir auf hosting
zugreifen. Schließlich wird die Funktion add_to_waitlist
mit pub
markiert
und wir können auf ihr Elternmodul zugreifen, sodass dieser Funktionsaufruf
klappt!
Beim relativen Pfad ist die Logik die gleiche wie beim absoluten Pfad, mit
Ausnahme des ersten Schritts: Anstatt von der Kistenwurzel auszugehen, beginnt
der Pfad mit front_of_house
. Das Modul front_of_house
wird innerhalb
desselben Moduls wie eat_at_restaurant
definiert, sodass der relative Pfad
ausgehend vom Modul, in dem eat_at_restaurant
definiert ist, funktioniert.
Weil hosting
und add_to_waitlist
nun mit pub
markiert sind, funktioniert
der Rest des Pfades, und dieser Funktionsaufruf ist gültig!
Wenn du vorhast, deine Bibliothekskiste weiterzugeben, damit andere Projekte deinen Code verwenden können, ist deine öffentliche API deine Übereinkunft mit den Benutzern deiner Kiste, die festlegt, wie sie mit deinem Code interagieren können. Es gibt viele Überlegungen zum Umgang mit Änderungen an deiner öffentlichen API, um es für andere einfacher zu machen, sich auf deine Kiste zu verlassen. Diese Überlegungen liegen außerhalb des Rahmens dieses Buches; wenn du an diesem Thema interessiert bist, lies die Rust API Guidelines.
Bewährte Praktiken für Pakete mit einer Binärdatei und einer Bibliothek
Wir haben bereits erwähnt, dass ein Paket sowohl eine Binärkistenwurzel src/main.rs als auch eine Bibliothekskistenwurzel src/lib.rs enthalten kann, und beide Kisten tragen standardmäßig den Paketnamen. Normalerweise haben Pakete mit diesem Muster, die sowohl eine Bibliothek als auch eine Binärkiste enthalten, gerade genug Code in der Binärkiste, um eine ausführbare Datei zu starten, die Code aus der Bibliothekskiste aufruft. Dadurch können andere Projekte von den meisten Funktionen des Pakets profitieren, da der Code der Bibliothekskiste gemeinsam genutzt werden kann.
Der Modulbaum sollte in src/lib.rs definiert werden. Dann können alle öffentlichen Elemente in der Binärkiste verwendet werden, indem die Pfade mit dem Namen des Pakets beginnen. Die binäre Kiste wird zu einem Benutzer der Bibliothekskiste, so wie eine vollständig externe Kiste die Bibliothekskiste verwenden würde: Sie kann nur die öffentliche API verwenden. Dies hilft dir, eine gute API zu entwerfen; Du bist nicht nur der Autor, sondern auch ein Kunde!
In Kapitel 12 werden wir diese organisatorische Praxis anhand eines Befehlszeilenprogramms demonstrieren, das sowohl eine Binärkiste als auch eine Bibliothekskiste enthält.
Relative Pfade mit super
beginnen
Wir können relative Pfade konstruieren, die im übergeordneten Modul beginnen
und nicht im aktuellen Modul oder der Kistenwurzel, indem wir super
am Anfang
des Pfades verwenden. Dies ist so, als würde man einen Dateisystempfad mit der
Syntax ..
beginnen. Das Verwenden von super
erlaubt es uns, auf ein Element
zu referenzieren, von dem wir wissen, dass es sich im übergeordneten Modul
befindet, was die Neuordnung des Modulbaums erleichtern kann, wenn das Modul
eng mit dem übergeordneten Modul verwandt ist, aber das übergeordnete Modul
eines Tages an eine andere Stelle im Modulbaum verschoben werden könnte.
Betrachte den Code in Codeblock 7-8, der die Situation nachbildet, in der ein
Koch eine falsche Bestellung korrigiert und persönlich zum Kunden bringt. Die
Funktion fix_incorrect_order
, die im Modul back_of_house
definiert ist,
ruft die im übergeordneten Modul definierte Funktion deliver_order
auf, indem
sie den Pfad zu deliver_order
angibt, der mit super
beginnt:
Dateiname: src/lib.rs
#![allow(unused)] fn main() { fn deliver_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); super::deliver_order(); } fn cook_order() {} } }
Die Funktion fix_incorrect_order
befindet sich im Modul back_of_house
,
sodass wir super
benutzen können, um zum Elternmodul von back_of_house
zu
gelangen, was in diesem Fall die Wurzel crate
ist. Von dort aus suchen wir
nach deliver_order
und finden es. Erfolg! Wir denken, dass das Modul
back_of_house
und die Funktion deliver_order
wahrscheinlich in der gleichen Beziehung zueinander stehen und zusammen verschoben werden, sollten wir uns dazu
entschließen, den Modulbaum der Kiste neu zu organisieren. Deshalb haben wir
super
verwendet, sodass wir in Zukunft weniger Codestellen zu aktualisieren
haben, wenn dieser Code in ein anderes Modul verschoben wird.
Strukturen und Aufzählungen öffentlich machen
Wir können auch pub
verwenden, um Strukturen und Aufzählungen als öffentlich
zu kennzeichnen, aber es gibt ein paar zusätzliche Details zur Verwendung von
pub
mit Strukturen und Aufzählungen. Wenn wir pub
vor einer
Struktur-Definition verwenden, machen wir die Struktur öffentlich, aber die
Felder der Struktur sind immer noch privat. Wir können jedes Feld von Fall zu
Fall öffentlich machen oder auch nicht. In Codeblock 7-9 haben wir eine
öffentliche Struktur back_of_house::Breakfast
mit einem öffentlichen Feld
toast
, aber einem privaten Feld seasonal_fruit
definiert. Dies ist der Fall
in einem Restaurant, in dem der Kunde die Brotsorte auswählen kann, die zu
einer Mahlzeit gehört, aber der Küchenchef entscheidet, welche Früchte die
Mahlzeit begleiten, je nach Saison und Vorrat. Das verfügbare Obst ändert sich
schnell, sodass die Kunden nicht wählen oder gar sehen können, welches Obst sie
bekommen.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { 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("Pfirsiche"), } } } } pub fn eat_at_restaurant() { // Bestelle im Sommer ein Frühstück mit Roggentoast let mut meal = back_of_house::Breakfast::summer("Roggen"); // Ändere unsere Meinung darüber, welche Brotsorte wir gerne hätten meal.toast = String::from("Weizen"); println!("Ich möchte {}-Toast", meal.toast); // Die nächste Zeile lässt sich nicht kompilieren, wenn wir sie nicht // auskommentieren; wir dürfen die Früchte der Saison, die wir mit der // Mahlzeit bekommen, weder sehen noch verändern. // meal.seasonal_fruit = String::from("Heidelbeeren"); } }
Da das Feld toast
in der Struktur back_of_house::Breakfast
öffentlich ist,
können wir in eat_at_restaurant
in das Feld toast
schreiben und lesen,
indem wir die Punktnotation verwenden. Beachte, dass wir das Feld
seasonal_fruit
in eat_at_restaurant
nicht verwenden können, weil
seasonal_fruit
privat ist. Versuche, die Kommentarzeichen in der Zeile, die
den Feldwert seasonal_fruit
modifiziert, zu entfernen, um zu sehen, welchen
Fehler du erhältst!
Beachte auch, dass, weil back_of_house::Breakfast
ein privates Feld hat, die
Struktur eine öffentliche Funktion (hier haben wir sie summer
genannt) zum
Erzeugen einer Instanz von Breakfast
bereitstellen muss. Wenn Breakfast
keine solche Funktion hätte, könnten wir keine Instanz von Breakfast
in
eat_at_restaurant
erzeugen, weil wir den Wert des privaten Feldes
seasonal_fruit
in eat_at_restaurant
nicht setzen könnten.
Wenn wir dagegen eine Aufzählung veröffentlichen, dann sind alle ihre
Varianten öffentlich. Wir brauchen nur das Schlüsselwort pub
vor dem
Schlüsselwort enum
, wie in Codeblock 7-10 gezeigt.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { 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; } }
Da wir die Aufzählung Appetizer
öffentlich gemacht haben, können wir die
Varianten Soup
und Salad
in eat_at_restaurant
verwenden.
Aufzählungen wären ohne öffentliche Varianten nicht sehr nützlich; es wäre
ärgerlich, alle Aufzählungs-Varianten stets mit pub
annotieren zu müssen,
daher sind die Aufzählungs-Varianten standardmäßig öffentlich. Strukturen sind
auch ohne öffentliche Felder nützlich, daher folgen Strukturfelder
standardmäßig der allgemeinen Regel, dass alles privat ist, es sei denn, es
wird mit pub
annotiert.
Es gibt noch eine weitere Situation mit pub
, die wir noch nicht behandelt
haben, und das ist unser letztes Modulsystem-Feature: Das Schlüsselwort use
.
Zuerst werden wir use
an sich behandeln, und dann zeigen wir, wie man pub
und use
kombiniert.