Contexte de la question :
Dans Rust, le système de modules contrôle strictement l'héritage et les dépendances entre fichiers et modules. Lorsque le projet s'agrandit, la tâche d'organiser des dépendances complexes entre différentes parties du code se pose souvent (par exemple, si des types d'un module sont nécessaires dans un autre). Dans d'autres langages (par exemple, C/C++), une telle situation peut conduire à des dépendances cycliques, des conflits implicites et des erreurs de compilation.
Problème :
En Rust, il n'est pas possible de créer des dépendances cycliques directes (chaque module ne peut faire référence qu'en amont ou en aval de la hiérarchie). Par conséquent, si, par exemple, le type A du module mod_a utilise le type B de mod_b, et que mod_b veut utiliser le type A, une situation compliquée se produit. Une organisation incorrecte peut empêcher la décomposition du projet en composants indépendants ou entraîner une duplication de code.
Solution :
Rust recommande d'introduire des types et des traits communs dans des modules ou crates séparés, et d'utiliser des références externes (chemins entièrement qualifiés) entre eux. Parfois, il est utile de déplacer les interfaces (trait) dans un lien intermédiaire distinct. Ainsi, les dépendances deviennent directionnelles et sont plus faciles à analyser au moment de la compilation.
Exemple de code :
// 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 pour Circle { fn draw(&self) { /* ... */ } } // src/scene.rs use crate::common::Drawable; pub struct Scene<T: Drawable> { pub items: Vec<T> }
Caractéristiques clés :
Peut-on utiliser pub use pour contourner les dépendances cycliques et importer un module depuis lui-même ?
Non, pub use n'est pas une solution pour les dépendances cycliques : il ne fonctionne que pour le réexportation d'un élément déjà défini. Si vous essayez de publier l'utilisation d'un module qui n'est pas encore compilé ou déclaré, une erreur de compilation surviendra.
Pourquoi la déclaration anticipée de modules, comme en C/C++, est-elle inacceptable ?
En Rust, il n'y a pas de mécanisme de déclaration anticipée des types ou modules : tous les modules, types et constantes doivent être déclarés et définis au moment de la compilation. Cela permet au compilateur de vérifier complètement la hiérarchie des types et d'éviter des conflits inattendus. La déclaration anticipée affaiblirait les garanties d'intégrité du système de types.
Peut-on réaliser des références croisées entre des structures de deux modules via Box ou Rc ?
Oui, si les types sont compatibles en matière de dépendances (par exemple, via des traits ou des enums communs), il est possible d'utiliser des références indirectes (Box, Rc, Arc) entre les structures. Cependant, cela ne dispense pas de la nécessité de les déclarer dans des domaines de visibilité qui ne créent pas réellement des modules cycliques.
Dans le projet, des modules séparés shapes/mod.rs et render/mod.rs ont été créés, mais les deux commencent à utiliser directement les types l'un de l'autre. Cela crée un cycle de dépendances, le compilateur renvoie une erreur d'importation non résolue.
Avantages :
Inconvénients :
Les types communs ont été extraits dans le module commun, les traits ont également été extraits, et les dépendances sont devenues unidirectionnelles (la scène dépend des formes, les formes et la scène dépendent du commun).
Avantages :
Inconvénients :