Geschichte der Frage:
In Rust kontrolliert das Modulsystem streng die Hierarchie und Abhängigkeiten zwischen Dateien und Modulen. Wenn ein Projekt wächst, kommt oft die Aufgabe auf, komplexe Abhängigkeiten zwischen Code-Teilen zu organisieren (z. B. wenn Typen aus einem Modul in einem anderen benötigt werden). In anderen Sprachen (z. B. C/C++) kann eine solche Situation zu zyklischen Abhängigkeiten, impliziten Konflikten und Kompilierungsfehlern führen.
Problem:
In Rust können keine direkten zyklischen Abhängigkeiten erstellt werden (jedes Modul kann nur nach oben oder unten in der Hierarchie verweisen). Daher entsteht eine Pattsituation, wenn z. B. Typ A aus dem Modul mod_a Typ B aus mod_b verwendet, während mod_b Typ A verwenden möchte. Eine falsche Organisation kann dazu führen, dass das Projekt nicht in unabhängige Komponenten aufgeteilt werden kann oder zu Code-Duplikation führt.
Lösung:
Rust empfiehlt, gemeinsame Typen und Traits in separate Module oder Crates auszulagern und zwischen ihnen externe Verweise (fully qualified paths) zu verwenden. Manchmal hilft es, Schnittstellen (Trait) in ein separates Zwischenmodul zu verschieben. Dadurch werden die Abhängigkeiten gerichtet und lassen sich einfacher zur Kompilierungszeit analysieren.
Beispielcode:
// src/common.rs pub trait Drawable { fn draw(&self); } // src/shapes/mod.rs use crate::common::Drawable; pub struct Circle { pub r: f64 } impl Drawable for Circle { fn draw(&self) { /* ... */ } } // src/scene.rs use crate::common::Drawable; pub struct Scene<T: Drawable> { pub items: Vec<T> }
Wesentliche Merkmale:
Kann man pub use verwenden, um zyklische Abhängigkeiten zu umgehen und ein Modul aus sich selbst zu importieren?
Nein, pub use ist keine Lösung für zyklische Abhängigkeiten: es funktioniert nur für die erneute Exportation bereits definierter Elemente. Wenn man versucht, ein Modul zu pub use-ing, das noch nicht kompiliert oder deklariert ist, gibt es einen Kompilierungsfehler.
Warum ist die Vorwärtsdeklaration von Modulen, wie in C/C++, nicht zulässig?
In Rust gibt es keinen Mechanismus zur Vorabdeklaration von Typen oder Modulen: Alle Module, Typen und Konstanten müssen zur Kompilierungszeit deklariert und definiert sein. Dies ermöglicht es dem Compiler, die Typ-Hierarchie vollständig zu überprüfen und unerwartete Konflikte zu vermeiden. Eine Vorwärtsdeklaration würde die Garantien der Integrität des Typsystems schwächen.
Können gegenseitige Verweise zwischen Strukturen zweier Module über Box oder Rc implementiert werden?
Ja, wenn die Typen in Bezug auf Abhängigkeiten übereinstimmen (z. B. über Trait oder gemeinsamen Enum), können indirekte Verweise (Box, Rc, Arc) zwischen Strukturen verwendet werden. Dies befreit jedoch nicht von der Anforderung, sie in Sichtbarkeitsbereichen zu deklarieren, die keine tatsächlich zyklischen Module erzeugen.
Im Projekt wurden separate Module shapes/mod.rs und render/mod.rs erstellt, aber beide beginnen, direkt Typen voneinander zu verwenden. Es entsteht ein Zyklus von Abhängigkeiten, der Compiler gibt einen Fehler unresolved import aus.
Vorteile:
Nachteile:
Gemeinsame Typen wurden in das Modul common ausgelagert, auch die Traits wurden ausgelagert, und die Abhängigkeiten sind einseitig geworden (scene hängt von shapes ab, shapes und scene von common).
Vorteile:
Nachteile: