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

Codeblock 7-11: Ein Modul mit use in den Gültigkeitsbereich bringen

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

Codeblock 7-12: Eine use-Anweisung gilt nur in dem Gültigkeitsbereich, in dem sie steht

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

Codeblock 7-13: Die Funktion add_to_waitlist mit use in den Gültigkeitsbereich bringen ist nicht idiomatisch.

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);
}

Codeblock 7-14: HashMap auf idiomatische Weise in den Gültigkeitsbereich bringen

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(())
}
}

Codeblock 7-15: Um zwei Typen mit dem gleichen Namen in denselben Gültigkeitsbereich zu bringen, müssen ihre übergeordneten Module angegeben werden.

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(())
}
}

Codeblock 7-16: Umbenennen eines Typs, wenn er mit dem Schlüsselwort as in den Gültigkeitsbereich gebracht wird

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

Codeblock 7-17: Bereitstellen eines Namens für externen Code zum Verwenden in einem neuen Gültigkeitsbereich mit pub use

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

Codeblock 7-18: Angeben eines verschachtelten Pfades, um mehrere Elemente mit demselben Präfix in den Gültigkeitsbereich zu bringen

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

Codeblock 7-19: Zwei use-Anweisungen, bei denen eine ein Teilpfad der anderen ist

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

Codeblock 7-20: Zusammenfassen der Pfade aus Codeblock 7-19 zu einer use-Anweisung

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.