Geschiedenis van de vraag:
In Rust controleert het modulesysteem strikt de hiërarchie en afhankelijkheden tussen bestanden en modules. Naarmate een project groeit, komt vaak de taak naar voren om complexe afhankelijkheden tussen codeonderdelen te organiseren (bijvoorbeeld als types uit één module nodig zijn in een andere). In andere talen (bijvoorbeeld C/C++) kan zo'n situatie leiden tot cyclische afhankelijkheden, impliciete conflicten en compileerfouten.
Probleem:
In Rust kunnen geen directe cyclische afhankelijkheden worden gemaakt (elke module kan alleen naar boven of naar beneden in de hiërarchie verwijzen). Daarom, als bijvoorbeeld type A uit module mod_a type B uit mod_b gebruikt, en mod_b type A wil gebruiken, ontstaat er een patstelling. Onjuiste organisatie kan leiden tot een onmogelijkheid om het project in onafhankelijke componenten te splitsen, of tot code-dubbelingen.
Oplossing:
Rust raadt aan om gemeenschappelijke types en traits in afzonderlijke modules of crates in te voeren, en tussen hen externe verwijzingen (fully qualified paths) te gebruiken. Soms helpt het om interfaces (traits) in een aparte tussenliggende schakel te plaatsen. Op deze manier worden afhankelijkheden richtinggevend en zijn ze gemakkelijker te analyseren tijdens het compileren.
Codevoorbeeld:
// 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> }
Belangrijkste kenmerken:
Kan pub use worden gebruikt om cyclische afhankelijkheden te omzeilen en een module vanuit zichzelf te importeren?
Nee, pub use is geen oplossing voor cyclische afhankelijkheden: het werkt alleen voor het opnieuw exporteren van een al gedefinieerd element. Als je probeert om pub use te gebruiken voor een module die nog niet is gecompileerd of gedefinieerd, zal dit een compilatiefout veroorzaken.
Waarom is forward declaration van modules, zoals in C/C++, niet toegestaan?
In Rust is er geen mechanisme voor de voorlopige verklaring van types of modules: alle modules, types en constanten moeten tijdens het compileren worden verklaard en gedefinieerd. Dit stelt de compiler in staat om de type-hiërarchie volledig te controleren en onverwachte conflicten te vermijden. Forward declaration zou de garanties van de integriteit van het typesysteem verzwakken.
Kan wederzijdse verwijzingen tussen structuren van twee modules worden geïmplementeerd via Box of Rc?
Ja, als de types overeenkomen in afhankelijkheden (bijvoorbeeld via trait of een gemeenschappelijke enum), kunnen gemedieerde verwijzingen (Box, Rc, Arc) tussen structuren worden gebruikt. Dit ontslaat echter niet van de vereiste om ze te verklaren in zichtbare gebieden die geen echt cyclische modules creëren.
In het project zijn aparte modules shapes/mod.rs en render/mod.rs gemaakt, maar beide beginnen elkaars types rechtstreeks te gebruiken. Er ontstaat een cyclische afhankelijkheid, en de compiler geeft een foutmelding over een unresolved import.
Voordelen:
Nadelen:
Gemeenschappelijke types zijn naar de module common verplaatst, en traits zijn ook verplaatst, waardoor de afhankelijkheden unidirectioneel zijn geworden (scene hangt af van shapes, shapes en scene hangen af van common).
Voordelen:
Nadelen: