web-dev-qa-db-fra.com

Fractionner un module sur plusieurs fichiers

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

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

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

use Math::Vector;

fn main() {
  // ...
}

Cependant, le système de modules de Rust (qui est un peu déroutant au début) ne fournit pas un moyen évident de le faire. Il semble ne vous autoriser que d'avoir tout votre module dans un seul fichier. Est-ce que ce n'est pas rustique? Si non, comment puis-je faire cela?

79
starscape

Très bien, a combattu mon compilateur pendant un certain temps et l'a finalement mis au travail (merci à BurntSushi d'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};
}

maths/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. NOTE: compilé avec 0.9, pas master.

16
starscape

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 cachant la structure de votre code dans des fichiers.

Je pense que la clé ici est de faire usage de pub use , ce qui vous permettra de réexporter les identifiants d’autres modules. Il y a un précédent pour ceci dans Rust's std::io caisse où certains types de sous-modules sont réexportés pour être utilisés dans std::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 rustc seul. Aujourd'hui, on utilise habituellement Cargo pour la plupart des cas d'utilisation. Bien que les éléments suivants soient toujours valables, certaines parties (par exemple, #![crate_type = ...]) peut sembler étrange. 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 ton 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 se produit. Nous avons défini un sous-module math::vector::vector_a qui implémente un type particulier de vecteur. Mais nous ne voulons pas que les clients de votre bibliothèque se soucient de l'existence d'un vector_a sous-module. Au lieu de cela, nous aimerions le rendre disponible dans le math::vector module. Ceci est fait avec pub use self::vector_a::VectorA, qui réexporte le vector_a::VectorA identifiant dans le module actuel.

Mais vous avez demandé comment procéder afin de pouvoir placer vos implémentations de vecteurs spéciales dans différents fichiers. C'est ce que le mod vector_b; ligne fait. Il indique au compilateur Rust) de rechercher un vector_b.rs fichier pour la mise en œuvre de ce module. Et bien sûr, voici notre src/vector_b.rs fichier:

#[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 VectorA et VectorB soient 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 le lancer avec:

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

En général, le chapitre "Crates and Modules" dans le livre Rust est plutôt bon. Il existe de nombreux exemples.

Enfin, le compilateur Rust cherche également automatiquement dans les sous-répertoires. Par exemple, le code ci-dessus fonctionnera sans modification avec cette structure de répertoires:

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

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

97
BurntSushi5

Les règles du module Rust sont les suivantes:

  1. Un fichier source est juste son propre module (sauf les fichiers spéciaux main.rs, lib.rs et mod.rs).
  2. Un répertoire est juste un composant de chemin de module.
  3. Le fichier mod.rs est juste le module du répertoire.

Le fichier matrix.rs1 dans le répertoire math est juste le module math::matrix. C'est facile. Ce que vous voyez sur votre système de fichiers se trouve également dans votre code source. Ceci est une correspondance un à un des chemins de fichiers et des chemins de modules2.

Vous pouvez donc importer une struct Matrix avec use math::matrix::Matrix, car la structure se trouve dans le fichier matrix.rs dans un répertoire math. Pas heureux? Vous préféreriez use math::Matrix; beaucoup au lieu de cela, n'est-ce pas? C'est possible. Ré-exportez l'identifiant math::matrix::Matrix en maths/mod.rs avec:

pub use self::math::Matrix;

Il y a une autre étape pour que cela fonctionne. Rust nécessite une déclaration de module pour charger le module. Ajouter un mod math; in 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 trompeur ici. Il n'y a pas besoin de caisses supplémentaires, sauf bien sûr, 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, matrix et complex, car math doit être chargé pour les réexporter. Une réexportation d'un identifiant ne fonctionne que si vous avez chargé le module de l'identifiant. Cela signifie, pour réexporter l'identifiant math::matrix::Matrix vous devez écrire mod matrix;. Vous pouvez le faire en maths/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;

Aaaand vous avez terminé.


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

2Java est différent. Vous déclarez le chemin avec package également. 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 cette information dans une déclaration en haut du fichier? Bien sûr, il est parfois plus facile de consulter rapidement le code source au lieu de localiser le système de fichiers du fichier. Je peux comprendre les gens qui disent que c'est moins déroutant.

27
nalply

Les puristes Rusts m'appelleront probablement hérétique et détesteront cette solution, mais c'est beaucoup plus simple: faites juste 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 compliquées d'exportation et de réécriture. Simple, efficace, pas de chichi.

14
hasvn