Pfade in den Gültigkeitsbereich bringen mit dem Schlüsselwort use
Die Pfade für den Aufruf von Funktionen auszuschreiben, kann lästig sein und
sich wiederholen. In Codeblock 7-7 mussten wir, unabhängig davon, ob wir den
absoluten oder relativen Pfad zur Funktion add_to_waitlist
wählten, jedes
Mal, wenn wir add_to_waitlist
aufrufen wollten, auch front_of_house
und
hosting
angeben. Glücklicherweise gibt es eine Möglichkeit, diesen Vorgang zu
vereinfachen: Wir können eine Verknüpfung zu einem Pfad mit dem Schlüsselwort
use
einmal erstellen und dann den kürzeren Namen überall sonst im
Gültigkeitsbereich verwenden.
In Codeblock 7-11 bringen wir das Modul crate::front_of_house::hosting
in den
Gültigkeitsbereich der Funktion eat_at_restaurant
, sodass wir nur noch
hosting::add_to_waitlist
angeben müssen, um die Funktion add_to_waitlist
in
eat_at_restaurant
aufzurufen.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Das Angeben von use
und einem Pfad in einem Gültigkeitsbereich ist ähnlich
dem Erstellen eines symbolischen Links im Dateisystem. Durch Hinzufügen von
use crate::front_of_house::hosting
in der Kistenwurzel ist hosting
nun ein
gültiger Name in diesem Gültigkeitsbereich, so als wäre das Modul hosting
in
der Kistenwurzel definiert worden. Pfade, die mit use
in den
Gültigkeitsbereich gebracht werden, überprüfen wie alle anderen Pfade auch die
Privatsphäre.
Beachte, dass use
nur die Verknüpfung für den jeweiligen Gültigkeitsbereich
erstellt, in dem use
vorkommt. Codeblock 7-12 verschiebt die Funktion
eat_at_restaurant
in ein neues untergeordnetes Modul namens customer
, das
dann einen anderen Gültigkeitsbereich als die use
-Anweisung hat, sodass der
Funktionsrumpf nicht kompiliert werden kann.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
Der Compilerfehler zeigt, dass die Verknüpfung innerhalb des Moduls customer
nicht mehr gilt:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
|
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
Beachte, dass es auch eine Warnung gibt, dass use
nicht mehr in seinem
Gültigkeitsbereich verwendet wird! Um dieses Problem zu beheben, verschiebe
use
auch innerhalb des Moduls customer
, oder referenziere die Verknüpfung
im übergeordneten Modul mit super::hosting
innerhalb des untergeordneten
Moduls customer
.
Idiomatische use
-Pfade erstellen
In Codeblock 7-11 hast du dich vielleicht gefragt, warum wir use crate::front_of_house::hosting
angegeben und dann hosting::add_to_waitlist
in eat_at_restaurant
aufgerufen haben, anstatt den use
-Pfad bis hin zur
Funktion add_to_waitlist
anzugeben, um dasselbe Ergebnis zu erzielen wie in
Codeblock 7-13.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
Obwohl sowohl Codeblock 7-11 als auch Codeblock 7-13 die gleiche Aufgabe
erfüllen, ist Codeblock 7-11 der idiomatische Weg, eine Funktion mit use
in
den Gültigkeitsbereich zu bringen. Wenn wir das Elternmodul der Funktion mit
use
in den Gültigkeitsbereich bringen, sodass wir das Elternmodul beim Aufruf
der Funktion angeben müssen, wird klar, dass die Funktion nicht lokal definiert
ist, während gleichzeitig die Wiederholung des vollständigen Pfades minimiert
wird. Im Code in Codeblock 7-13 ist unklar, wo add_to_waitlist
definiert ist.
Wenn andererseits Strukturen, Aufzählungen und andere Elemente mit use
eingebracht werden, ist es idiomatisch, den vollständigen Pfad anzugeben.
Codeblock 7-14 zeigt den idiomatischen Weg, die Struktur HashMap
der
Standardbibliothek in den Gültigkeitsbereich einer binären Kiste zu bringen.
Dateiname: src/main.rs
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
Es gibt keinen triftigen Grund für dieses Idiom: Es ist einfach eine Konvention, die entstanden ist, und die Leute haben sich daran gewöhnt, Rust-Code auf diese Weise zu lesen und zu schreiben.
Die Ausnahme von diesem Idiom ist, wenn wir zwei gleichnamige Elemente mit
use
in den Gültigkeitsbereich bringen, denn das lässt Rust nicht zu. In
Codeblock 7-15 wird gezeigt, wie zwei Result
-Typen mit gleichem Namen, aber
unterschiedlichen Elternmodulen in den Gültigkeitsbereich gebracht werden und
wie auf sie verwiesen werden kann.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { use std::fmt; use std::io; fn function1() -> fmt::Result { // --abschneiden-- Ok(()) } fn function2() -> io::Result<()> { // --abschneiden-- Ok(()) } }
Wie du sehen kannst, unterscheidet die Verwendung der übergeordneten Module die
beiden Result
-Typen. Wenn wir stattdessen use std::fmt::Result
und
use std::io::Result
angeben würden, hätten wir zwei Result
-Typen im selben
Gültigkeitsbereich und Rust wüsste nicht, welchen wir beim Verwenden von
Result
meinten.
Mit dem Schlüsselwort as
neue Namen vergeben
Es gibt eine andere Lösung für das Problem, zwei Typen desselben Namens mit
use
in den gleichen Gültigkeitsbereich zu bringen: Hinter dem Pfad können wir
as
und einen neuen lokalen Namen oder Alias für den Typ angeben. Codeblock
7-16 zeigt eine weitere Möglichkeit, den Code in Codeblock 7-15 zu schreiben,
indem einer der beiden Result
-Typen mittels as
umbenannt wird.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { use std::fmt::Result; use std::io::Result as IoResult; fn function1() -> Result { // --abschneiden-- Ok(()) } fn function2() -> IoResult<()> { // --abschneiden-- Ok(()) } }
In der zweiten use
-Anweisung wählten wir den neuen Namen IoResult
für den
Typ std::io::Result
, der nicht im Konflikt zum ebenfalls von uns in den
Gültigkeitsbereich gebrachten Result
aus std::fmt
steht. Codeblock 7-15
und Codeblock 7-16 gelten als idiomatisch, die Wahl liegt also bei dir!
Rück-Exportieren von Namen mit pub use
Wenn wir einen Namen mit dem Schlüsselwort use
in den Gültigkeitsbereich
bringen, ist der im neuen Gültigkeitsbereich verfügbare Name privat. Damit der
Code, der unseren Code aufruft, auf diesen Namen verweisen kann, als wäre er im
Gültigkeitsbereich dieses Codes definiert worden, können wir pub
und use
kombinieren. Diese Technik wird Rück-Exportieren (re-exporting) genannt, weil
wir ein Element in den Gültigkeitsbereich bringen, dieses Element aber auch
anderen zur Verfügung stellen, um es in ihren Gültigkeitsbereich zu bringen.
Codeblock 7-17 zeigt den Code in Codeblock 7-11, wobei use
im Wurzelmodul in
pub use
geändert wurde.
Dateiname: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Vor dieser Änderung musste externer Code die Funktion add_to_waitlist
mit dem
Pfad restaurant::front_of_house::hosting::add_to_waitlist()
aufrufen, was
zudem erfordert hätte, dass das Modul front_of_house
als pub
gekennzeichnet
ist. Da aber pub use
das Modul hosting
aus dem Wurzel-Modul re-exportiert
hat, kann externer Code nun stattdessen den Pfad
restaurant::hosting::add_to_waitlist()
verwenden.
Der Rück-Export ist nützlich, wenn sich die interne Struktur deines Codes von
dem unterscheidet, wie Programmierer, die deinen Code
aufrufen, über die Domäne denken würden. In der Restaurantmetapher denken die
Betreiber des Restaurants zum Beispiel an die „Vorderseite des Hauses“ und die
„Rückseite des Hauses“. Mit pub use
können wir unseren Code mit einer
Struktur schreiben, aber eine andere Struktur veröffentlichen. Auf diese Weise
ist unsere Bibliothek für Programmierer, die an der Bibliothek arbeiten, und
Programmierer, die die Bibliothek aufrufen, gut organisiert. Ein weiteres
Beispiel für pub use
und wie es sich auf die Dokumentation deiner Kiste
auswirkt, werden wir im Abschnitt „Mit pub use
eine benutzerfreundliche
öffentliche API exportieren“ in Kapitel 14 betrachten.
Verwenden externer Pakete
In Kapitel 2 programmierten wir ein Ratespielprojekt, das ein externes Paket
namens rand
benutzte, um Zufallszahlen zu generieren. Um rand
in unserem
Projekt zu verwenden, fügten wir diese Zeile zu Cargo.toml hinzu:
Dateiname: Cargo.toml
rand = "0.8.5"
Das Hinzufügen von rand
als Abhängigkeit in Cargo.toml weist Cargo an, das
Paket rand
und alle Abhängigkeiten von crates.io
herunterzuladen und rand
für unser Projekt verfügbar zu machen.
Um dann Definitionen von rand
in den Gültigkeitsbereich unseres Pakets
aufzunehmen, haben wir eine Zeile mit use
hinzugefügt, die mit dem
Kistennamen rand
beginnt und die Elemente auflistet, die wir in den
Gültigkeitsbereich bringen wollten. Erinnere dich, dass wir im Abschnitt
„Generieren einer Geheimzahl“ in Kapitel 2 das Merkmal Rng
in den
Gültigkeitsbereich gebracht und die Funktion rand::thread_rng
aufgerufen
haben:
use std::io; use rand::Rng; fn main() { println!("Rate die Zahl!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("Die geheime Zahl ist: {secret_number}"); println!("Bitte gib deine Vermutung ein."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Fehler beim Lesen einer Zeile"); println!("Du hast geraten: {guess}"); }
Mitglieder der Rust-Gemeinschaft haben viele Pakete unter
crates.io zur Verfügung gestellt und wenn du eines davon
in dein Paket aufnimmst, sind die gleichen Schritte erforderlich: Liste sie
in der Datei Cargo.toml deines Pakets auf und verwende use
, um Elemente aus
ihren Kisten in den Gültigkeitsbereich zu bringen.
Beachte, dass die Standardbibliothek std
ebenfalls eine Kiste ist, die nicht
zu unserem Paket gehört. Da die Standardbibliothek mit der Sprache Rust
ausgeliefert wird, brauchen wir Cargo.toml nicht zu ändern, um std
einzubinden. Aber wir müssen use
verwenden, um Elemente von dort in den
Gültigkeitsbereich unseres Pakets zu bringen. Zum Beispiel würden wir für
HashMap
diese Zeile verwenden:
#![allow(unused)] fn main() { use std::collections::HashMap; }
Dies ist ein absoluter Pfad, der mit std
, dem Namen der
Standard-Bibliothekskiste, beginnt.
Verschachtelte Pfade verwenden, um lange use
-Listen zu vereinfachen
Wenn wir mehrere in der gleichen Kiste oder im gleichen Modul definierte
Elemente verwenden, kann das Auflisten jedes Elements in einer eigenen Zeile
viel vertikalen Platz in unseren Dateien einnehmen. Zum Beispiel bringen diese
beiden use
-Anweisungen, die wir im Ratespiel in Codeblock 2-4 hatten,
Elemente aus std
in den Gültigkeitsbereich:
Dateiname: src/main.rs
#![allow(unused)] fn main() { // --abschneiden-- use std::cmp::Ordering; use std::io; // --abschneiden-- }
Stattdessen können wir verschachtelte Pfade verwenden, um die gleichen Elemente in einer Zeile in den Gültigkeitsbereich zu bringen. Wir tun dies, indem wir den gemeinsamen Teil des Pfades angeben, gefolgt von zwei Doppelpunkten und dann geschweiften Klammern um Liste der Pfadteile, die sich unterscheiden, wie in Codeblock 7-18 gezeigt.
Dateiname: src/main.rs
#![allow(unused)] fn main() { // --abschneiden-- use std::{cmp::Ordering, io}; // --abschneiden-- }
In größeren Programmen kann das Einbeziehen vieler Elemente aus derselben Kiste
oder demselben Modul in den Gültigkeitsbereich durch verschachtelte Pfade die
Anzahl der separaten use
-Anweisungen um ein Vielfaches reduzieren!
Wir können einen verschachtelten Pfad auf jeder Ebene in einem Pfad verwenden,
was nützlich ist, wenn zwei use
-Anweisungen kombiniert werden, die sich einen
Teilpfad teilen. Beispielsweise zeigt Codeblock 7-19 zwei use
-Anweisungen:
Eine, die std::io
in den Gültigkeitsbereich bringt, und eine, die
std::io::Write
in den Gültigkeitsbereich bringt.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { use std::io; use std::io::Write; }
Der gemeinsame Teil dieser beiden Pfade ist std::io
und das ist der
vollständige erste Pfad. Um diese beiden Pfade zu einer einzigen
use
-Anweisung zu verschmelzen, können wir self
im verschachtelten Pfad
verwenden, wie in Codeblock 7-20 gezeigt wird.
Dateiname: src/lib.rs
#![allow(unused)] fn main() { use std::io::{self, Write}; }
Diese Zeile bringt std::io
und std::io::Write
in den Gültigkeitsbereich.
Der Stern-Operator (glob)
Wenn wir alle öffentlichen Elemente, die in einem Pfad definiert sind, in den
Gültigkeitsbereich bringen wollen, können wir diesen Pfad gefolgt vom
Stern-Operator *
angeben:
#![allow(unused)] fn main() { use std::collections::*; }
Diese use
-Anweisung bringt alle öffentlichen Elemente, die in
std::collections
definiert sind, in den aktuellen Gültigkeitsbereich. Sei
vorsichtig beim Verwenden des Stern-Operators! Er kann es schwieriger machen,
zu erkennen, welche Namen in den Gültigkeitsbereich fallen und wo ein in deinem
Programm verwendeter Name definiert wurde.
Der Stern-Operator wird oft beim Testen verwendet, um alles, was getestet wird,
in das Modul tests
zu bringen. Wir werden darüber im Abschnitt „Tests
schreiben“ in Kapitel 11 sprechen. Der Stern-Operator wird
manchmal auch als Teil des Präludiumsmusters (prelude pattern) verwendet: Siehe
Standardbibliotheksdokumentation für weitere Informationen
zu diesem Muster.