Teilen Sie ein Modul auf mehrere Dateien auf

101

Ich möchte ein Modul mit mehreren Strukturen haben, jede in einer eigenen Datei. Am Beispiel eines MathModuls:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Ich möchte, dass sich jede Struktur in demselben Modul befindet, das ich aus meiner Hauptdatei verwenden würde, wie folgt:

use Math::Vector;

fn main() {
  // ...
}

Das Modulsystem von Rust (das anfangs etwas verwirrend ist) bietet jedoch keine offensichtliche Möglichkeit, dies zu tun. Es scheint nur möglich zu sein, Ihr gesamtes Modul in einer Datei zu haben. Ist das nicht rustikal? Wenn nicht, wie mache ich das?

Sternenlandschaft
quelle
1
Ich interpretierte "Ich möchte ein Modul mit mehreren Strukturen haben, jede in einer eigenen Datei." Dies bedeutet, dass Sie jede Strukturdefinition in einer eigenen Datei haben möchten.
BurntSushi5
1
Dies würde nicht als rustikal angesehen, obwohl das Modulsystem eine solche Strukturierung sicherlich zulässt. Im Allgemeinen ist es vorzuziehen, dass ein Modulpfad direkt einem Dateisystempfad entspricht, z. B. foo::bar::Bazsollte struct in foo/bar.rsoder definiert werden foo/bar/mod.rs.
Chris Morgan

Antworten:

111

Das Modulsystem von Rust ist tatsächlich unglaublich flexibel und ermöglicht es Ihnen, jede gewünschte Struktur verfügbar zu machen, während Sie verbergen, wie Ihr Code in Dateien strukturiert ist.

Ich denke, der Schlüssel hier ist die Verwendung pub use, mit der Sie Bezeichner aus anderen Modulen erneut exportieren können. Es gibt einen Präzedenzfall dafür in Rusts std::ioKiste, in der einige Typen aus Untermodulen zur Verwendung in erneut exportiert werdenstd::io .

Bearbeiten (25.08.2019): Der folgende Teil der Antwort wurde vor einiger Zeit geschrieben. Es wird erklärt, wie Sie eine solche Modulstruktur einrichtenrustc allein eingerichtet wird. Heutzutage würde man normalerweise Fracht für die meisten Anwendungsfälle verwenden. Während das Folgende noch gültig ist, #![crate_type = ...]scheinen einige Teile davon (z. B. ) seltsam zu sein. Dies ist nicht die empfohlene Lösung.

Um Ihr Beispiel anzupassen, könnten wir mit dieser Verzeichnisstruktur beginnen:

src/
  lib.rs
  vector.rs
main.rs

Hier ist dein main.rs:

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}

Und dein src/lib.rs:

#[crate_id = "math"];
#[crate_type = "lib"];

pub mod vector; // exports the module defined in vector.rs

Und schließlich src/vector.rs:

// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;

mod vector_b; // private sub-module defined in vector_b.rs

mod vector_a { // private sub-module defined in place
    #[derive(Debug)]
    pub struct VectorA {
        xs: Vec<i64>,
    }

    impl VectorA {
        pub fn new() -> VectorA {
            VectorA { xs: vec![] }
        }
    }
}

Und hier geschieht die Magie. Wir haben ein Untermodul definiert math::vector::vector_a, in dem eine spezielle Art von Vektor implementiert ist. Wir möchten jedoch nicht, dass Kunden Ihrer Bibliothek sich darum kümmern, dass es ein vector_aUntermodul gibt . Stattdessen möchten wir es im math::vectorModul verfügbar machen . Dies geschieht mit pub use self::vector_a::VectorA, wodurch die vector_a::VectorAKennung im aktuellen Modul erneut exportiert wird.

Sie haben jedoch gefragt, wie dies zu tun ist, damit Sie Ihre speziellen Vektorimplementierungen in verschiedenen Dateien ablegen können. Das macht die mod vector_b;Leitung. Es weist den Rust-Compiler an, nach einer vector_b.rsDatei für die Implementierung dieses Moduls zu suchen . Und natürlich ist hier unsere src/vector_b.rsAkte:

#[derive(Debug)]
pub struct VectorB {
    xs: Vec<i64>,
}

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}

Aus Sicht des Kunden ist die Tatsache, dass VectorAund VectorBin zwei verschiedenen Modulen in zwei verschiedenen Dateien definiert sind, völlig undurchsichtig.

Wenn Sie sich im selben Verzeichnis wie befinden main.rs, sollten Sie es ausführen können mit:

rustc src/lib.rs
rustc -L . main.rs
./main

Im Allgemeinen ist das Kapitel "Kisten und Module" im Rust-Buch ziemlich gut. Es gibt viele Beispiele.

Schließlich sucht der Rust-Compiler auch automatisch in Unterverzeichnissen nach Ihnen. Der obige Code funktioniert beispielsweise unverändert mit dieser Verzeichnisstruktur:

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs

Die Befehle zum Kompilieren und Ausführen bleiben ebenfalls gleich.

BurntSushi5
quelle
Ich glaube, Sie haben falsch verstanden, was ich mit "Vektor" gemeint habe. Ich sprach von Vektor wie in der mathematischen Größe , nicht von der Datenstruktur. Außerdem verwende ich nicht die neueste Version von Rost, da es ein bisschen mühsam ist, auf Fenstern zu bauen.
Sternenlandschaft
+1 war nicht genau das, was ich brauchte, zeigte mir aber in die richtige Richtung.
Sternenlandschaft
@ EpicPineapple In der Tat! Und ein Vec kann verwendet werden, um solche Vektoren darzustellen. (Für größere N natürlich.)
BurntSushi5
1
@EpicPineapple Können Sie erklären, was meine Antwort verpasst hat, damit ich sie aktualisieren kann? Ich kämpfe darum, den Unterschied zwischen Ihrer und meiner Antwort zu erkennen, außer sie math::Vec2anstelle von zu verwenden math::vector::Vec2. (dh das gleiche Konzept, aber ein Modul tiefer.)
BurntSushi5
1
Ich sehe diese Kriterien nicht in Ihrer Frage. Soweit ich sehen kann, habe ich die gestellte Frage beantwortet. (Was wirklich gefragt hat, wie man Module von Dateien trennt.) Tut mir leid, dass es nicht mit Rust 0.9 funktioniert, aber das hängt mit dem Gebiet zusammen, eine instabile Sprache zu verwenden.
BurntSushi5
38

Die Regeln für das Rust-Modul lauten:

  1. Eine Quelldatei ist nur ein eigenes Modul (mit Ausnahme der Spezialdateien main.rs, lib.rs und mod.rs).
  2. Ein Verzeichnis ist nur eine Modulpfadkomponente.
  3. Die Datei mod.rs ist nur das Modul des Verzeichnisses.

Die Datei matrix.rs 1 im Verzeichnis math ist nur das Modul math::matrix. Es ist einfach. Was Sie in Ihrem Dateisystem sehen, finden Sie auch in Ihrem Quellcode. Dies ist eine Eins-zu-Eins-Entsprechung von Dateipfaden und Modulpfaden 2 .

Sie können also eine Struktur Matrixmit importieren use math::matrix::Matrix, da sich die Struktur in der Datei matrix.rs in einem Verzeichnis math befindet. Nicht glücklich? Sie würden use math::Matrix;stattdessen sehr viel bevorzugen , nicht wahr? Es ist möglich. Exportieren Sie den Bezeichner math::matrix::Matrixin math / mod.rs erneut mit:

pub use self::math::Matrix;

Es gibt noch einen weiteren Schritt, um dies zum Laufen zu bringen. Rust benötigt eine Moduldeklaration, um das Modul zu laden . Fügen Sie ein mod math;in main.rs. Wenn Sie dies nicht tun, erhalten Sie beim Importieren eine Fehlermeldung vom Compiler:

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

Der Hinweis ist hier irreführend. Es sind keine zusätzlichen Kisten erforderlich, außer natürlich, dass Sie wirklich beabsichtigen, eine separate Bibliothek zu schreiben.

Fügen Sie dies oben in main.rs hinzu:

mod math;
pub use math::Matrix;

Die Moduldeklaration ist auch notwendig für die Submodule vector, matrixund complex, weil mathBedürfnisse sie laden sie erneut zu exportieren. Ein Reexport eines Bezeichners funktioniert nur, wenn Sie das Modul des Bezeichners geladen haben. Dies bedeutet, dass math::matrix::MatrixSie den zu schreibenden Bezeichner erneut exportieren müssen mod matrix;. Sie können dies in math / mod.rs tun. Erstellen Sie daher die Datei mit folgendem Inhalt:

mod vector;
pub use self::vector::Vector;

mod matrix;
pub use self::matrix::Matrix;

mod complex;
pub use self::complex::Complex;

Aaaund du bist fertig.


1 Quelldateinamen beginnen normalerweise mit einem Kleinbuchstaben in Rust. Deshalb benutze ich matrix.rs und nicht Matrix.rs.

2 Java ist anders. Sie deklarieren den Pfad auch mit package. Es ist überflüssig. Der Pfad ist bereits aus dem Speicherort der Quelldatei im Dateisystem ersichtlich. Warum sollten Sie diese Informationen in einer Erklärung oben in der Datei wiederholen? Natürlich ist es manchmal einfacher, einen kurzen Blick auf den Quellcode zu werfen, als den Dateisystemspeicherort der Datei herauszufinden. Ich kann Leute verstehen, die sagen, dass es weniger verwirrend ist.

nalply
quelle
23

Rusts Puristen werden mich wahrscheinlich als Ketzer bezeichnen und diese Lösung hassen, aber das ist viel einfacher: Machen Sie einfach alles in einer eigenen Datei und verwenden Sie dann das Makro " include! " In mod.rs:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

Auf diese Weise erhalten Sie keine zusätzlichen verschachtelten Module und vermeiden komplizierte Export- und Umschreibregeln. Einfach, effektiv, ohne viel Aufhebens.

hasvn
quelle
1
Sie haben gerade den Namespace rausgeschmissen. Wenn Sie eine Datei auf eine Weise ändern, die nichts mit einer anderen zu tun hat, können jetzt andere Dateien beschädigt werden. Ihre Verwendung von "Verwendung" wird undicht (dh alles ist wie use super::*). Sie können Code nicht vor anderen Dateien verbergen (was für die unsichere Verwendung sicherer Abstraktionen wichtig ist)
Demur Rumed
11
Ja, aber genau das wollte ich in diesem Fall: Haben Sie mehrere Dateien, die sich für Namespace-Zwecke nur als eine verhalten. Ich befürworte dies nicht für jeden Fall, aber es ist eine nützliche Problemumgehung, wenn Sie sich aus irgendeinem Grund nicht mit der Methode "Ein Modul pro Datei" befassen möchten.
Hasvn
Das ist großartig, ich habe einen Teil meines Moduls, der nur intern, aber in sich geschlossen ist, und das hat den Trick getan. Ich werde versuchen, die richtige Modullösung auch zum Laufen zu bringen, aber es ist bei weitem nicht so einfach.
rjh
5
Es ist mir egal, als Ketzer bezeichnet zu werden, Ihre Lösung ist praktisch!
Segelfisch009
20

Okay, ich habe eine Weile gegen meinen Compiler gekämpft und ihn endlich zum Laufen gebracht (danke an BurntSushi für den Hinweis pub use.

main.rs:

use math::Vec2;
mod math;

fn main() {
  let a = Vec2{x: 10.0, y: 10.0};
  let b = Vec2{x: 20.0, y: 20.0};
}

math / mod.rs:

pub use self::vector::Vec2;
mod vector;

math / vector.rs

use std::num::sqrt;

pub struct Vec2 {
  x: f64,
  y: f64
}

impl Vec2 {
  pub fn len(&self) -> f64 {
    sqrt(self.x * self.x + self.y * self.y) 
  }

  // other methods...
}

Andere Strukturen könnten auf die gleiche Weise hinzugefügt werden. HINWEIS: Kompiliert mit 0.9, nicht mit Master.

Sternenlandschaft
quelle
4
Beachten Sie, dass die Nutzung von mod math;in main.rsPaaren Ihr mainProgramm mit Ihrer Bibliothek. Wenn Sie möchten, dass Ihr mathModul unabhängig ist, müssen Sie es separat kompilieren und mit verknüpfen extern crate math(wie in meiner Antwort gezeigt). In Rust 0.9 ist es möglich, dass extern mod mathstattdessen die Syntax lautet .
BurntSushi5
20
Es wäre wirklich fair gewesen, die Antwort von BurntSushi5 als die richtige zu markieren.
IluTov
2
@NSAddict Nein. Um Module von Dateien zu trennen, müssen Sie keine separate Kiste erstellen. Es ist überentwickelt.
Nalply
1
Warum ist dies nicht die am besten gewählte Antwort? Bei der Frage wurde gefragt, wie das Projekt in einige Dateien aufgeteilt werden soll, was so einfach ist, wie diese Antwort zeigt, und nicht, wie es in Kisten aufgeteilt werden soll. Dies ist schwieriger und wird von @ BurntSushi5 beantwortet (möglicherweise wurde die Frage bearbeitet?). ..
Renato
6
Die Antwort von @ BurntSushi5 sollte die akzeptierte Antwort gewesen sein. Es ist sozial umständlich und vielleicht sogar gemein, eine Frage zu stellen, eine sehr schöne Antwort zu erhalten, sie dann als separate Antwort zusammenzufassen und Ihre Zusammenfassung als akzeptierte Antwort zu markieren.
Hasanyasin
3

Ich möchte hier hinzufügen, wie Sie Rust-Dateien einschließen, wenn sie tief verschachtelt sind. Ich habe folgende Struktur:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

Wie greifen Sie darauf zu sink.rsoder toilet.rsvon ?main.rs ?

Wie andere bereits erwähnt haben, kennt Rust keine Dateien. Stattdessen sieht es alles als Module und Submodule. Um auf die Dateien im Badezimmerverzeichnis zuzugreifen, müssen Sie sie exportieren oder nach oben verschieben. Dazu geben Sie einen Dateinamen mit dem Verzeichnis an, auf das Sie zugreifen möchten, undpub mod filename_inside_the_dir_without_rs_ext in der Datei.

Beispiel.

// sink.rs
pub fn run() { 
    println!("Wash my hands for 20 secs!");
}

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Erstellen Sie eine Datei mit dem Namen bathroom.rsim homeVerzeichnis:

  2. Exportieren Sie die Dateinamen:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Erstellen Sie eine Datei mit dem Namen home.rsnebenmain.rs

  4. pub mod die Datei bath.rs

    // home.rs
    pub mod bathroom;
  5. Innerhalb main.rs

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    mod home;
    
    fn main() {
        home::bathroom::sink::run();
    }

    use Anweisungen können auch verwendet werden:

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    use home::bathroom::{sink, toilet};
    
    fn main() {
        sink::run();
        sink::toilet();
    }

Einbindung anderer Geschwistermodule (Dateien) in Submodule

Wenn Sie sink.rsfrom verwenden toilet.rsmöchten, können Sie das Modul aufrufen, indem Sie die Schlüsselwörter selfoder superangeben.

// inside toilet.rs
use self::sink;
pub fn run() {
  sink::run();
  println!("Ahhh... This is sooo relaxing.")
}

Endgültige Verzeichnisstruktur

Sie würden mit so etwas enden:

|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

Die obige Struktur funktioniert nur ab Rust 2018. Die folgende Verzeichnisstruktur gilt auch für 2018, aber so hat 2015 funktioniert.

|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs

In dem home/mod.rsist das gleiche wie ./home.rsund home/bathroom/mod.rsist das gleiche wie home/bathroom.rs. Rust hat diese Änderung vorgenommen, da der Compiler verwirrt wäre, wenn Sie eine Datei mit demselben Namen wie das Verzeichnis einfügen würden. Die Version 2018 (die zuerst gezeigte) behebt diese Struktur.

Weitere Informationen finden Sie in diesem Repo und in diesem YouTube-Video eine allgemeine Erklärung.

Eine letzte Sache ... vermeiden Sie Bindestriche! Verwenden Sie snake_casestattdessen.

Wichtige Notiz

Sie müssen alle Dateien nach oben verschieben, auch wenn tiefe Dateien von Top-Level-Dateien nicht benötigt werden.

Dies bedeutet, dass Sie sink.rssie entdecken toilet.rsmüssen , um sie zu entdecken , indem Sie die oben genannten Methoden anwendenmain.rs anwenden!

Mit anderen Worten, tun pub mod sink;oder im use self::sink; Inneren toilet.rswird nicht arbeiten , es sei denn Sie haben sie den ganzen Weg bis zu ausgesetzt main.rs!

Denken Sie deshalb immer daran, Ihre Dateien nach oben zu verschieben!

Jose A.
quelle
2
... das ist wahnsinnig verworren im Vergleich zu C ++, das etwas sagt
Joseph Garvin