Einführung
Willkommen zu Paths for Referring to an Item in the Module Tree. Dieser Lab ist ein Teil des Rust Buchs. Du kannst deine Rust-Fähigkeiten in LabEx üben.
In diesem Lab lernen wir, dass Pfade in Rust verwendet werden, um auf Elemente im Modultree zu verweisen, und dass sie in der Form von absoluten Pfaden oder relativen Pfaden auftreten können.
Pfade zum Verweisen auf ein Element im Modultree
Um Rust zu zeigen, wo es ein Element im Modultree finden soll, verwenden wir einen Pfad auf die gleiche Weise, wie wir einen Pfad verwenden, wenn wir einen Dateisystem navigieren. Um eine Funktion aufzurufen, müssen wir ihren Pfad kennen.
Ein Pfad kann zwei Formen annehmen:
- Ein absoluter Pfad ist der vollständige Pfad, der von einer Kistenwurzel aus beginnt; für Code aus einem externen Kasten beginnt der absolute Pfad mit dem Kastennamen, und für Code aus dem aktuellen Kasten beginnt er mit dem Literal
crate. - Ein relativer Pfad beginnt bei dem aktuellen Modul und verwendet
self,superoder einen Bezeichner im aktuellen Modul.
Sowohl absolute als auch relative Pfade werden von einem oder mehreren Bezeichnern, die durch Doppelpunkte (::) getrennt sind, gefolgt.
Wenn wir zurück zu Listing 7-1 gehen, sagen wir, dass wir die add_to_waitlist-Funktion aufrufen möchten. Dies ist dasselbe wie fragen: Was ist der Pfad zur add_to_waitlist-Funktion? Listing 7-3 enthält Listing 7-1 mit einigen der Module und Funktionen entfernt.
Wir werden zwei Wege zeigen, um die add_to_waitlist-Funktion aus einer neuen Funktion, eat_at_restaurant, die in der Kistenwurzel definiert ist, aufzurufen. Diese Pfade sind korrekt, aber es bleibt noch ein weiteres Problem übrig, das verhindert, dass dieses Beispiel so kompiliert wird. Wir werden gleich erklären, warum.
Die eat_at_restaurant-Funktion ist Teil der öffentlichen API unseres Bibliothekskastens, daher markieren wir sie mit dem Schlüsselwort pub. In "Exposing Paths with the pub Keyword" werden wir genauer auf pub eingehen.
Dateiname: src/lib.rs
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();
}
Listing 7-3: Aufrufen der add_to_waitlist-Funktion mit absoluten und relativen Pfaden
Wenn wir die add_to_waitlist-Funktion zum ersten Mal in eat_at_restaurant aufrufen, verwenden wir einen absoluten Pfad. Die add_to_waitlist-Funktion ist in demselben Kasten wie eat_at_restaurant definiert, was bedeutet, dass wir das Schlüsselwort crate verwenden können, um einen absoluten Pfad zu beginnen. Wir fügen dann jedes der folgenden Module hinzu, bis wir zu add_to_waitlist gelangen. Man kann sich ein Dateisystem mit derselben Struktur vorstellen: Wir würden den Pfad /front_of_house/hosting/add_to_waitlist angeben, um das add_to_waitlist-Programm auszuführen; das Verwenden des crate-Namens, um von der Kistenwurzel aus zu beginnen, ist wie das Verwenden von /, um von der Dateisystemwurzel in Ihrer Shell aus zu beginnen.
Wenn wir die add_to_waitlist zum zweiten Mal in eat_at_restaurant aufrufen, verwenden wir einen relativen Pfad. Der Pfad beginnt mit front_of_house, dem Namen des Moduls, das auf derselben Ebene des Modultrees wie eat_at_restaurant definiert ist. Hier entspräche der Dateisystempfad dem Pfad front_of_house/hosting/add_to_waitlist. Ein Start mit einem Modulnamen bedeutet, dass der Pfad relativ ist.
Die Entscheidung, ob ein relativer oder absoluter Pfad verwendet werden soll, ist eine Entscheidung, die Sie aufgrund Ihres Projekts treffen müssen, und es hängt davon ab, ob Sie wahrscheinlicher die Elementdefinitionscode separat oder zusammen mit dem Code verschieben, der das Element verwendet. Beispielsweise würden wir, wenn wir das front_of_house-Modul und die eat_at_restaurant-Funktion in ein Modul namens customer_experience verschieben, den absoluten Pfad zu add_to_waitlist aktualisieren müssen, aber der relative Pfad würde immer noch gültig sein. Wenn wir die eat_at_restaurant-Funktion jedoch separat in ein Modul namens dining verschieben würden, würde der absolute Pfad zum add_to_waitlist-Aufruf gleich bleiben, aber der relative Pfad müsste aktualisiert werden. Unser allgemeines Präferenz besteht darin, absolute Pfade anzugeben, da es wahrscheinlicher ist, dass wir Codedefinitionen und Elementaufrufe unabhängig voneinander verschieben möchten.
Lassen Sie uns versuchen, Listing 7-3 zu kompilieren und herauszufinden, warum es noch nicht kompilieren wird! Die Fehler, die wir erhalten, werden in Listing 7-4 gezeigt.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: Modul `hosting` ist privat
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ privates Modul
|
note: Das Modul `hosting` ist hier definiert
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: Modul `hosting` ist privat
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ privates Modul
|
note: Das Modul `hosting` ist hier definiert
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
Listing 7-4: Compilerfehler beim Erstellen des Codes in Listing 7-3
Die Fehlermeldungen sagen, dass das Modul hosting privat ist. Mit anderen Worten, wir haben die korrekten Pfade für das hosting-Modul und die add_to_waitlist-Funktion, aber Rust lässt uns sie nicht verwenden, weil es keinen Zugang zu den privaten Abschnitten hat. In Rust sind alle Elemente (Funktionen, Methoden, Strukturen, Enums, Module und Konstanten) standardmäßig für übergeordnete Module privat. Wenn Sie ein Element wie eine Funktion oder eine Struktur privat machen möchten, legen Sie es in ein Modul.
Elemente in einem übergeordneten Modul können die privaten Elemente in Untermodulen nicht verwenden, aber Elemente in Untermodulen können die Elemente in ihren Vorfahrenmodulen verwenden. Dies liegt daran, dass Untermodule ihre Implementierungsdetails umschließen und verbergen, aber die Untermodule können den Kontext sehen, in dem sie definiert sind. Um mit unserer Metapher fortzufahren, denken Sie sich die Privatsphäre-Regeln wie das Hinterzimmer eines Restaurants: Was dort passiert, ist privat für Restaurantkunden, aber Büromanager können alles im Restaurant sehen und tun, das sie betreiben.
Rust hat die Modulsystemfunktion so gewählt, dass das Verbergen innerer Implementierungsdetails die Standard-Einstellung ist. Auf diese Weise wissen Sie, welche Teile des inneren Codes Sie ändern können, ohne den äußeren Code zu brechen. Rust gibt Ihnen jedoch die Möglichkeit, innere Teile des Codes von Untermodulen an übergeordnete Vorfahrenmodule zu exponieren, indem Sie das Schlüsselwort pub verwenden, um ein Element öffentlich zu machen.
Exponieren von Pfaden mit dem Schlüsselwort pub
Lassen Sie uns zurückkehren zum Fehler in Listing 7-4, der uns mitgeteilt hat, dass das Modul hosting privat ist. Wir möchten, dass die eat_at_restaurant-Funktion im übergeordneten Modul auf die add_to_waitlist-Funktion im Untermodul zugreifen kann, daher markieren wir das Modul hosting mit dem Schlüsselwort pub, wie in Listing 7-5 gezeigt.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
--snip--
Listing 7-5: Deklarieren des Moduls hosting als pub, um es aus eat_at_restaurant zu verwenden
Leider führt der Code in Listing 7-5 immer noch zu Compilerfehlern, wie in Listing 7-6 gezeigt.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: Funktion `add_to_waitlist` ist privat
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private Funktion
|
note: Die Funktion `add_to_waitlist` ist hier definiert
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: Funktion `add_to_waitlist` ist privat
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private Funktion
|
note: Die Funktion `add_to_waitlist` ist hier definiert
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
Listing 7-6: Compilerfehler beim Erstellen des Codes in Listing 7-5
Was ist passiert? Das Hinzufügen des Schlüsselworts pub vor mod hosting macht das Modul öffentlich. Mit dieser Änderung können wir, wenn wir auf front_of_house zugreifen können, auch auf hosting zugreifen. Aber der Inhalt von hosting ist immer noch privat; das Machen des Moduls öffentlich macht seine Inhalte nicht öffentlich. Das pub-Schlüsselwort auf einem Modul lässt nur Code in seinen Vorfahrenmodulen auf es verweisen, nicht auf seinen inneren Code zuzugreifen. Da Module Container sind, können wir mit nur dem Machen des Moduls öffentlich nicht viel tun; wir müssen noch weiter gehen und eine oder mehrere der Elemente innerhalb des Moduls ebenfalls öffentlich machen.
Die Fehler in Listing 7-6 sagen, dass die add_to_waitlist-Funktion privat ist. Die Privatsphäre-Regeln gelten für Structs, Enums, Funktionen und Methoden sowie für Module.
Lassen Sie uns auch die add_to_waitlist-Funktion öffentlich machen, indem wir das Schlüsselwort pub vor ihrer Definition hinzufügen, wie in Listing 7-7.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
--snip--
Listing 7-7: Hinzufügen des Schlüsselworts pub zu mod hosting und fn add_to_waitlist ermöglicht uns, die Funktion aus eat_at_restaurant aufzurufen.
Jetzt wird der Code kompilieren! Um zu verstehen, warum das Hinzufügen des Schlüsselworts pub uns ermöglicht, diese Pfade in add_to_waitlist in Bezug auf die Privatsphäre-Regeln zu verwenden, betrachten wir die absoluten und die relativen Pfade.
Im absoluten Pfad beginnen wir mit crate, der Wurzel unseres Kastenmodultrees. Das Modul front_of_house ist in der Kistenwurzel definiert. Obwohl front_of_house nicht öffentlich ist, können wir, da die eat_at_restaurant-Funktion in demselben Modul wie front_of_house definiert ist (d.h., eat_at_restaurant und front_of_house sind Geschwister), von eat_at_restaurant auf front_of_house verweisen. Als nächstes ist das Modul hosting mit pub markiert. Wir können auf das übergeordnete Modul von hosting zugreifen, daher können wir auf hosting zugreifen. Schließlich ist die add_to_waitlist-Funktion mit pub markiert und wir können auf ihr übergeordnetes Modul zugreifen, daher funktioniert dieser Funktionsaufruf!
Im relativen Pfad ist die Logik dasselbe wie im absoluten Pfad, mit Ausnahme des ersten Schritts: Anstatt von der Kistenwurzel aus zu beginnen, beginnt der Pfad von front_of_house. Das Modul front_of_house ist innerhalb desselben Moduls wie eat_at_restaurant definiert, daher funktioniert der relative Pfad, der von dem Modul ausgeht, in dem eat_at_restaurant definiert ist. Dann, da hosting und add_to_waitlist mit pub markiert sind, funktioniert der restliche Pfad und dieser Funktionsaufruf ist gültig!
Wenn Sie vorhaben, Ihre Bibliothekskiste zu teilen, so dass andere Projekte Ihren Code verwenden können, ist Ihre öffentliche API Ihr Vertrag mit den Benutzern Ihrer Kiste, der bestimmt, wie sie mit Ihrem Code interagieren können. Es gibt viele Überlegungen bei der Verwaltung von Änderungen an Ihrer öffentlichen API, um es Menschen einfacher zu machen, von Ihrer Kiste abzuhängen. Diese Überlegungen liegen außerhalb des Rahmens dieses Buches; wenn Sie an diesem Thema interessiert sind, finden Sie die Rust API Guidelines unter https://rust-lang.github.io/api-guidelines.
Best Practices für Pakete mit einem Binärprogramm und einer Bibliothek
Wir haben erwähnt, dass ein Paket sowohl eine Binärkistenwurzel
src/main.rsals auch eine Bibliothekskistenwurzelsrc/lib.rsenthalten kann, und beide Kisten werden standardmäßig den Paketnamen haben. Typischerweise werden Pakete mit diesem Muster, das sowohl eine Bibliothek als auch ein Binärpaket enthält, nur so viel Code im Binärpaket haben, um ein ausführbares Programm zu starten, das Code mit der Bibliothekskiste aufruft. Dadurch können andere Projekte von der meiste Funktionalität profitieren, die das Paket bietet, da der Code der Bibliothekskiste geteilt werden kann.Der Modultree sollte in
src/lib.rsdefiniert werden. Dann können alle öffentlichen Elemente im Binärpaket verwendet werden, indem Pfade mit dem Namen des Pakets begonnen werden. Das Binärpaket wird zu einem Benutzer der Bibliothekskiste, genauso wie ein völlig externes Paket die Bibliothekskiste verwenden würde: Es kann nur die öffentliche API verwenden. Dies hilft Ihnen, eine gute API zu entwerfen; nicht nur sind Sie der Autor, Sie sind auch ein Client!Im Kapitel 12 werden wir diese Organisationspraxis mit einem Befehlszeilenprogramm demonstrieren, das sowohl ein Binärpaket als auch eine Bibliothekskiste enthalten wird.
Starten von relativen Pfaden mit super
Wir können relative Pfade konstruieren, die im übergeordneten Modul beginnen, anstatt im aktuellen Modul oder der Kistenwurzel, indem wir super am Anfang des Pfads verwenden. Dies ist wie das Starten eines Dateisystempfads mit der ..-Syntax. Das Verwenden von super ermöglicht es uns, auf ein Element zu verweisen, das wir wissen, sich im übergeordneten Modul befindet, was das Umorganisieren des Modultrees einfacher macht, wenn das Modul eng mit dem übergeordneten Modul zusammenhängt, aber das übergeordnete Modul vielleicht eines Tages an einen anderen Ort im Modultree verschoben wird.
Betrachten Sie den Code in Listing 7-8, der die Situation modelliert, in der ein Koch eine falsche Bestellung behebt und sie persönlich an den Kunden bringt. Die Funktion fix_incorrect_order, die im Modul back_of_house definiert ist, ruft die Funktion deliver_order, die im übergeordneten Modul definiert ist, auf, indem sie den Pfad zu deliver_order angibt, beginnend mit super.
Dateiname: 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: Aufrufen einer Funktion mit einem relativen Pfad, der mit super beginnt
Die fix_incorrect_order-Funktion befindet sich im Modul back_of_house, daher können wir super verwenden, um zum übergeordneten Modul von back_of_house zu gelangen, was in diesem Fall crate, die Wurzel, ist. Von dort suchen wir nach deliver_order und finden es. Erfolg! Wir denken, dass das Modul back_of_house und die deliver_order-Funktion wahrscheinlicher in derselben Beziehung zueinander bleiben und zusammen verschoben werden, sollten wir uns entscheiden, den Modultree der Kiste umzuarbeiten. Daher haben wir super verwendet, damit wir in Zukunft weniger Stellen im Code aktualisieren müssen, wenn dieser Code in ein anderes Modul verschoben wird.
Machen von Structs und Enums öffentlich
Wir können auch pub verwenden, um Structs und Enums als öffentlich zu kennzeichnen, aber es gibt einige zusätzliche Details bei der Verwendung von pub mit Structs und Enums. Wenn wir pub vor einer Struct-Definition verwenden, machen wir die Struct öffentlich, aber die Felder der Struct bleiben immer noch privat. Wir können jedes Feld individuell öffentlich oder privat machen. In Listing 7-9 haben wir eine öffentliche back_of_house::Breakfast-Struct definiert, mit einem öffentlichen toast-Feld, aber einem privaten seasonal_fruit-Feld. Dies modelliert den Fall in einem Restaurant, in dem der Kunde die Art von Brot wählen kann, das zu einem Gericht gehört, aber der Koch entscheidet, welches Obst mit dem Gericht serviert wird, basierend auf der Saison und dem Vorrat. Das verfügbare Obst ändert sich schnell, sodass die Kunden das Obst nicht auswählen oder sogar nicht sehen können, welches sie erhalten.
Dateiname: 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() {
// Bestellen Sie ein Frühstück im Sommer mit Roggenbrot
let mut meal = back_of_house::Breakfast::summer("Rye");
// Ändern Sie sich über das Brot, das wir möchten
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// Die nächste Zeile wird nicht kompilieren, wenn wir sie entsperren; wir dürfen
// nicht sehen oder ändern das saisonale Obst, das mit dem Gericht serviert wird
// meal.seasonal_fruit = String::from("blueberries");
}
Listing 7-9: Eine Struct mit einigen öffentlichen und einigen privaten Feldern
Da das toast-Feld in der back_of_house::Breakfast-Struct öffentlich ist, können wir in eat_at_restaurant auf das toast-Feld schreiben und lesen, indem wir die Punktnotation verwenden. Beachten Sie, dass wir das seasonal_fruit-Feld in eat_at_restaurant nicht verwenden können, da seasonal_fruit privat ist. Versuchen Sie, die Zeile zum Ändern des seasonal_fruit-Feldwerts zu entsperren, um zu sehen, welchen Fehler Sie erhalten!
Beachten Sie auch, dass die back_of_house::Breakfast-Struct ein privates Feld hat, daher muss die Struct eine öffentliche assoziierte Funktion bereitstellen, die eine Instanz von Breakfast konstruiert (wir haben sie hier summer genannt). Wenn Breakfast keine solche Funktion hätte, könnten wir in eat_at_restaurant keine Instanz von Breakfast erstellen, da wir den Wert des privaten seasonal_fruit-Felds in eat_at_restaurant nicht setzen könnten.
Im Gegensatz dazu, wenn wir einen Enum als öffentlich machen, sind alle seine Varianten dann öffentlich. Wir brauchen nur das pub vor dem enum-Schlüsselwort, wie in Listing 7-10 gezeigt.
Dateiname: 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: Das Kennzeichnen eines Enums als öffentlich macht alle seine Varianten öffentlich.
Da wir den Appetizer-Enum als öffentlich gemacht haben, können wir die Soup- und Salad-Varianten in eat_at_restaurant verwenden.
Enums sind nicht sehr nützlich, es sei denn, ihre Varianten sind öffentlich; es wäre lästig, in jedem Fall alle Enum-Varianten mit pub zu annotieren, daher ist die Standardeinstellung für Enum-Varianten, öffentlich zu sein. Structs sind oft nützlich, auch wenn ihre Felder nicht öffentlich sind, daher folgen Struct-Felder der allgemeinen Regel, dass alles standardmäßig privat ist, es sei denn, es wird mit pub annotiert.
Es gibt noch eine Situation, die pub betrifft, die wir noch nicht behandelt haben, und das ist unsere letzte Modulsystem-Funktion: das use-Schlüsselwort. Wir werden zuerst use für sich allein behandeln und dann zeigen, wie pub und use kombiniert werden können.
Zusammenfassung
Herzlichen Glückwunsch! Sie haben das Lab "Paths for Referring to an Item in the Module Tree" abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.