Diviser un module sur plusieurs fichiers

103

Je veux avoir un module avec plusieurs structures, chacune dans son propre fichier. En utilisant un Mathmodule comme exemple:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Je veux que chaque structure soit dans le même module, que j'utiliserais à partir de mon fichier principal, comme ceci:

use Math::Vector;

fn main() {
  // ...
}

Cependant, le système de modules de Rust (qui est un peu déroutant pour commencer) ne fournit pas un moyen évident de le faire. Il semble ne vous permettre d'avoir tout votre module que dans un seul fichier. N'est-ce pas rustique? Sinon, comment dois-je procéder?

paysage étoilé
la source
1
J'ai interprété "Je veux avoir un module avec plusieurs structures, chacune dans son propre fichier." pour signifier que vous vouliez chaque définition de structure dans son propre fichier.
BurntSushi5
1
Cela ne serait pas considéré comme rustique, bien que le système de modules permette certainement une telle structuration. Il est généralement préférable qu'un chemin de module corresponde directement à un chemin de système de fichiers, par exemple struct foo::bar::Bazdoit être défini dans foo/bar.rsou foo/bar/mod.rs.
Chris Morgan

Réponses:

112

Le système de modules de Rust est en fait incroyablement flexible et vous permettra d'exposer le type de structure que vous souhaitez tout en masquant la structure de votre code dans des fichiers.

Je pense que la clé ici est de l'utiliser pub use, ce qui vous permettra de réexporter des identifiants à partir d'autres modules. Il existe un précédent pour cela dans la std::iocaisse de Rust où certains types de sous-modules sont réexportés pour être utilisés dansstd::io .

Edit (2019-08-25): la partie suivante de la réponse a été écrite il y a un certain temps. Il explique comment configurer une telle structure de module avec rustcseul. Aujourd'hui, on utilise généralement Cargo pour la plupart des cas d'utilisation. Bien que ce qui suit soit toujours valide, certaines parties de celui-ci (par exemple #![crate_type = ...]) peuvent sembler étranges. Ce n'est pas la solution recommandée.

Pour adapter votre exemple, nous pourrions commencer par cette structure de répertoires:

src/
  lib.rs
  vector.rs
main.rs

Voici votre main.rs:

extern crate math;

use math::vector;

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

Et votre src/lib.rs:

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

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

Et enfin 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![] }
        }
    }
}

Et c'est là que la magie opère. Nous avons défini un sous-module math::vector::vector_aqui a une implémentation d'un type spécial de vecteur. Mais nous ne voulons pas que les clients de votre bibliothèque se soucient de l'existence d'un vector_asous-module. Au lieu de cela, nous aimerions le rendre disponible dans le math::vectormodule. Ceci est fait avec pub use self::vector_a::VectorA, qui réexporte l' vector_a::VectorAidentifiant dans le module courant.

Mais vous avez demandé comment faire cela pour pouvoir mettre vos implémentations vectorielles spéciales dans différents fichiers. C'est ce que fait la mod vector_b;ligne. Il demande au compilateur Rust de rechercher un vector_b.rsfichier pour l'implémentation de ce module. Et bien sûr, voici notre src/vector_b.rsfichier:

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

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

Du point de vue du client, le fait que VectorAet VectorBsoient définis dans deux modules différents dans deux fichiers différents est complètement opaque.

Si vous êtes dans le même répertoire que main.rs, vous devriez pouvoir l'exécuter avec:

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

En général, le chapitre "Crates and Modules" du livre Rust est assez bon. Il existe de nombreux exemples.

Enfin, le compilateur Rust recherche également automatiquement les sous-répertoires. Par exemple, le code ci-dessus fonctionnera inchangé avec cette structure de répertoire:

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

Les commandes à compiler et à exécuter restent également les mêmes.

BurntSushi5
la source
Je crois que vous avez mal compris ce que je voulais dire par «vecteur». Je parlais du vecteur comme de la quantité mathématique , pas de la structure des données. De plus, je n'utilise pas la dernière version de rust, car c'est un peu pénible de construire sur Windows.
starscape
+1 Ce n'était pas exactement ce dont j'avais besoin, mais m'a orienté dans la bonne direction.
starscape
@EpicPineapple en effet! Et un Vec peut être utilisé pour représenter de tels vecteurs. (Pour un N plus grand, bien sûr.)
BurntSushi5
1
@EpicPineapple Pourriez-vous m'expliquer ce que ma réponse a manqué pour que je puisse la mettre à jour? J'ai du mal à voir la différence entre votre réponse et la mienne autre que d'utiliser math::Vec2au lieu de math::vector::Vec2. (c'est-à-dire, même concept mais un module plus profond.)
BurntSushi5
1
Je ne vois pas ce critère dans votre question. Autant que je sache, j'ai répondu à la question posée. (Ce qui demandait vraiment comment séparer les modules des fichiers.) Désolé, cela ne fonctionne pas sur Rust 0.9, mais cela vient avec l'utilisation d'un langage instable.
BurntSushi5
41

Les règles du module Rust sont:

  1. Un fichier source est juste son propre module (à l'exception des fichiers spéciaux main.rs, lib.rs et mod.rs).
  2. Un répertoire n'est qu'un composant de chemin de module.
  3. Le fichier mod.rs est juste le module du répertoire.

Le fichier matrix.rs 1 dans le répertoire math n'est que le module math::matrix. C'est facile. Ce que vous voyez sur votre système de fichiers, vous le trouvez également dans votre code source. Il s'agit d'une correspondance un à un des chemins de fichiers et des chemins de module 2 .

Vous pouvez donc importer une structure Matrixavec use math::matrix::Matrix, car la structure est à l'intérieur du fichier matrix.rs dans un répertoire math. Pas heureux? Vous préféreriez use math::Matrix;beaucoup à la place, n'est-ce pas? C'est possible. math::matrix::MatrixRéexportez l'identifiant dans math / mod.rs avec:

pub use self::math::Matrix;

Il y a une autre étape pour que cela fonctionne. Rust a besoin d'une déclaration de module pour charger le module. Ajoutez un mod math;dans main.rs. Si vous ne le faites pas, vous obtenez un message d'erreur du compilateur lors de l'importation comme ceci:

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

L'indice est ici trompeur. Il n'y a pas besoin de caisses supplémentaires, sauf bien sûr que vous avez vraiment l'intention d'écrire une bibliothèque séparée.

Ajoutez ceci en haut de main.rs:

mod math;
pub use math::Matrix;

La déclaration de module est également nécessaire pour les sous-modules vector, matrixet complex, car mathdoit les charger pour les réexporter. Une réexportation d'un identifiant ne fonctionne que si vous avez chargé le module de l'identifiant. Cela signifie que pour réexporter l'identifiant, math::matrix::Matrixvous devez écrire mod matrix;. Vous pouvez le faire dans math / mod.rs. Créez donc le fichier avec ce contenu:

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

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

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

Aaa et vous avez terminé.


1 Les noms de fichiers source commencent généralement par une lettre minuscule dans Rust. C'est pourquoi j'utilise matrix.rs et non Matrix.rs.

2 Java est différent. Vous déclarez également le chemin avec package. C'est redondant. Le chemin est déjà évident à partir de l'emplacement du fichier source dans le système de fichiers. Pourquoi répéter ces informations dans une déclaration en haut du dossier? Bien sûr, il est parfois plus facile de jeter un coup d'œil rapide sur le code source au lieu de trouver l'emplacement du système de fichiers du fichier. Je peux comprendre les gens qui disent que c'est moins déroutant.

nalply
la source
25

Les puristes de Rusts m'appelleront probablement un hérétique et détesteront cette solution, mais c'est beaucoup plus simple: faites simplement chaque chose dans son propre fichier, puis utilisez la macro " include! " Dans mod.rs:

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

De cette façon, vous n'obtenez aucun module imbriqué ajouté et évitez les règles complexes d'exportation et de réécriture. Simple, efficace, sans chichi.

hasvn
la source
1
Vous venez de jeter l'espace de noms. Changer un fichier d'une manière sans rapport avec un autre peut désormais casser d'autres fichiers. Votre utilisation de «utiliser» devient fuyante (c'est-à-dire que tout est comme use super::*). Vous ne pouvez pas masquer le code d'autres fichiers (ce qui est important pour l'utilisation d'abstractions sûres non sécurisées)
Demur Rumed
13
Oui, mais c'est exactement ce que je voulais dans ce cas: avoir plusieurs fichiers qui se comportent comme un seul à des fins d'espacement de noms. Je ne préconise pas cela pour tous les cas, mais c'est une solution de contournement utile si vous ne voulez pas vous occuper de la méthode "un module par fichier", pour une raison quelconque.
hasvn le
C'est génial, j'ai une partie de mon module qui est uniquement interne mais autonome, et cela a fait l'affaire. J'essaierai également de faire fonctionner la solution de module appropriée, mais ce n'est pas aussi simple.
rjh
7
je m'en fiche d'être appelé hérétique, votre solution est pratique!
sailfish009
21

D'accord, j'ai combattu mon compilateur pendant un moment et je l'ai finalement fait fonctionner (merci à BurntSushi pour l'avoir signalé 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...
}

D'autres structures pourraient être ajoutées de la même manière. REMARQUE: compilé avec 0.9, pas master.

paysage étoilé
la source
4
Notez que votre utilisation mod math;en main.rscouple de votre mainprogramme avec votre bibliothèque. Si vous voulez que votre mathmodule soit indépendant, vous devrez le compiler séparément et le lier avec extern crate math(comme indiqué dans ma réponse). Dans Rust 0.9, il est possible que la syntaxe soit à la extern mod mathplace.
BurntSushi5
20
Il aurait vraiment été juste de marquer la réponse de BurntSushi5 comme étant la bonne.
IluTov
2
@NSAddict Non. Pour séparer les modules des fichiers, vous n'avez pas besoin de créer une caisse séparée. C'est sur-conçu.
nalply
1
Pourquoi n'est-ce pas la réponse la plus votée? La question demandait comment diviser le projet en quelques fichiers, ce qui est aussi simple que cette réponse le montre, pas comment le diviser en caisses, ce qui est plus difficile et est ce à quoi @ BurntSushi5 a répondu (peut-être que la question a été modifiée?). ..
Renato
6
La réponse de @ BurntSushi5 aurait dû être la réponse acceptée. Il est socialement gênant et peut-être même méchant de poser une question, d'obtenir une très belle réponse, puis de la résumer comme une réponse séparée et de marquer votre résumé comme la réponse acceptée.
hasanyasin
5

J'aimerais ajouter ici comment vous incluez les fichiers Rust lorsqu'ils sont profondément imbriqués. J'ai la structure suivante:

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

Comment y accédez-vous sink.rsou toilet.rsdepuis main.rs?

Comme d'autres l'ont mentionné, Rust n'a aucune connaissance des fichiers. Au lieu de cela, il considère tout comme des modules et des sous-modules. Pour accéder aux fichiers dans le répertoire de la salle de bain, vous devez les exporter ou les placer en haut. Pour ce faire, spécifiez un nom de fichier avec le répertoire auquel vous souhaitez accéder et pub mod filename_inside_the_dir_without_rs_extà l'intérieur du fichier.

Exemple.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Créez un fichier appelé bathroom.rsdans le homerépertoire:

  2. Exportez les noms de fichiers:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Créez un fichier appelé à home.rscôté demain.rs

  4. pub mod le fichier bathroom.rs

    // home.rs
    pub mod bathroom;
  5. Dans 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 les instructions peuvent également être utilisées:

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

Inclure d'autres modules frères (fichiers) dans des sous-modules

Dans le cas où vous souhaitez utiliser sink.rsfrom toilet.rs, vous pouvez appeler le module en spécifiant les mots clés selfou super.

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

Structure finale du répertoire

Vous vous retrouveriez avec quelque chose comme ça:

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

La structure ci-dessus ne fonctionne qu'à partir de Rust 2018. La structure de répertoires suivante est également valable pour 2018, mais c'est ainsi que 2015 fonctionnait auparavant.

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

Dans lequel home/mod.rsest le même que ./home.rset home/bathroom/mod.rsest le même que home/bathroom.rs. Rust a apporté cette modification car le compilateur serait confus si vous incluiez un fichier portant le même nom que le répertoire. La version 2018 (celle illustrée en premier) corrige cette structure.

Consultez ce dépôt pour plus d'informations et cette vidéo YouTube pour une explication générale.

Une dernière chose ... évitez les traits d'union! Utilisez snake_caseplutôt.

Note importante

Vous devez placer tous les fichiers vers le haut, même si les fichiers profonds ne sont pas requis par les fichiers de niveau supérieur.

Cela signifie que pour sink.rsles découvrir toilet.rs, vous devez les barricader en utilisant les méthodes ci-dessus jusqu'à main.rs!

En d' autres termes, faire pub mod sink;ou à l' use self::sink; intérieur toilet.rsvous ne le travail à moins que vous les avez exposé tout le chemin jusqu'à main.rs!

Par conséquent, n'oubliez pas de placer vos fichiers au sommet!

José A
la source
2
... c'est incroyablement alambiqué par rapport au C ++, qui dit quelque chose
Joseph Garvin