Suddividi un modulo su più file


102

Voglio avere un modulo con più strutture in esso, ciascuna nel proprio file. Utilizzando un Mathmodulo come esempio:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Voglio che ogni struttura si trovi nello stesso modulo, che userei dal mio file principale, in questo modo:

use Math::Vector;

fn main() {
  // ...
}

Tuttavia il sistema di moduli di Rust (che è un po 'confuso all'inizio) non fornisce un modo ovvio per farlo. Sembra che ti consenta di avere l'intero modulo in un solo file. Questo non è rustico? In caso contrario, come posso farlo?


1
Ho interpretato "Voglio avere un modulo con più strutture in esso, ciascuna nel proprio file". a significare che volevi ogni definizione di struttura nel proprio file.
BurntSushi5

1
Questo non sarebbe considerato rustico, anche se il sistema a moduli certamente consente tale strutturazione. In genere è preferibile che un percorso di modulo corrisponda direttamente a un percorso di file system, ad esempio, la struttura foo::bar::Bazdovrebbe essere definita in foo/bar.rso foo/bar/mod.rs.
Chris Morgan

Risposte:


111

Il sistema di moduli di Rust è in realtà incredibilmente flessibile e ti consentirà di esporre qualsiasi tipo di struttura desideri nascondendo il modo in cui il tuo codice è strutturato nei file.

Penso che la chiave qui sia da usare pub use, che ti permetterà di riesportare gli identificatori da altri moduli. C'è un precedente per questo nel std::iocrate di Rust, dove alcuni tipi di sotto-moduli vengono riesportati per l'uso instd::io .

Modifica (2019-08-25): la parte seguente della risposta è stata scritta parecchio tempo fa. Spiega come impostare una struttura di questo tipo con rustcsolo. Oggi, di solito si usa Cargo per la maggior parte dei casi d'uso. Anche se quanto segue è ancora valido, alcune parti (ad esempio #![crate_type = ...]) potrebbero sembrare strane. Questa non è la soluzione consigliata.

Per adattare il tuo esempio, potremmo iniziare con questa struttura di directory:

src/
  lib.rs
  vector.rs
main.rs

Ecco il tuo main.rs:

extern crate math;

use math::vector;

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

E il tuo src/lib.rs:

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

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

E infine 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![] }
        }
    }
}

Ed è qui che avviene la magia. Abbiamo definito un sottomodulo math::vector::vector_ache ha alcune implementazioni di un tipo speciale di vettore. Ma non vogliamo che i clienti della tua libreria si preoccupino della presenza di un vector_asottomodulo. Invece, vorremmo renderlo disponibile nel math::vectormodulo. Questo viene fatto con pub use self::vector_a::VectorA, che riesporta l' vector_a::VectorAidentificatore nel modulo corrente.

Ma hai chiesto come farlo in modo da poter inserire le tue implementazioni vettoriali speciali in file diversi. Questo è ciò mod vector_b;che fa la linea. Indica al compilatore Rust di cercare un vector_b.rsfile per l'implementazione di quel modulo. E abbastanza sicuro, ecco il nostro src/vector_b.rsfile:

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

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

Dal punto di vista del cliente, il fatto che VectorAe VectorBsiano definiti in due diversi moduli in due file differenti è completamente opaco.

Se ti trovi nella stessa directory di main.rs, dovresti essere in grado di eseguirlo con:

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

In generale, il capitolo "Crates and Modules" nel libro Rust è abbastanza buono. Ci sono molti esempi.

Infine, il compilatore Rust cerca automaticamente anche nelle sottodirectory. Ad esempio, il codice precedente funzionerà invariato con questa struttura di directory:

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

Anche i comandi da compilare ed eseguire rimangono gli stessi.


Credo che tu abbia frainteso cosa intendevo per "vettore". Stavo parlando del vettore come nella quantità matematica , non nella struttura dei dati. Inoltre, non sto eseguendo l'ultima versione di ruggine, perché è un po 'una seccatura costruire su Windows.
starscape

+1 non era esattamente quello di cui avevo bisogno, ma mi ha indirizzato nella giusta direzione.
starscape

@EpicPineapple Indeed! E un Vec può essere utilizzato per rappresentare tali vettori. (Per N più grandi, ovviamente.)
BurntSushi5

1
@EpicPineapple Potresti spiegare cosa ha perso la mia risposta in modo che io possa aggiornarla? Faccio fatica a vedere la differenza tra la tua risposta e la mia diversa dall'usare math::Vec2invece di math::vector::Vec2. (cioè, stesso concetto ma un modulo più profondo.)
BurntSushi5

1
Non vedo questi criteri nella tua domanda. Per quanto posso vedere, ho risposto alla domanda posta. (Che in realtà stava chiedendo come separare i moduli dai file.) Mi dispiace che non funzioni su Rust 0.9, ma questo viene fornito con il territorio dell'uso di un linguaggio instabile.
BurntSushi5

39

Le regole del modulo Rust sono:

  1. Un file sorgente è solo il suo modulo (eccetto i file speciali main.rs, lib.rs e mod.rs).
  2. Una directory è solo un componente del percorso del modulo.
  3. Il file mod.rs è solo il modulo della directory.

Il file matrix.rs 1 nella directory math è solo il modulo math::matrix. È facile. Quello che vedi sul tuo filesystem lo trovi anche nel tuo codice sorgente. Questa è una corrispondenza uno a uno dei percorsi dei file e dei percorsi dei moduli 2 .

Quindi puoi importare una struttura Matrixcon use math::matrix::Matrix, perché la struttura è all'interno del file matrix.rs in una directory math. Non felice? Preferiresti use math::Matrix;molto invece, non è vero? È possibile. Esportare nuovamente l'identificatore math::matrix::Matrixin math / mod.rs con:

pub use self::math::Matrix;

C'è un altro passaggio per farlo funzionare. Rust ha bisogno di una dichiarazione del modulo per caricare il modulo. Aggiungi un mod math;in main.rs. Se non lo fai, ricevi un messaggio di errore dal compilatore durante l'importazione in questo modo:

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

Il suggerimento qui è fuorviante. Non c'è bisogno di casse aggiuntive, tranne ovviamente che hai davvero intenzione di scrivere una libreria separata.

Aggiungi questo all'inizio di main.rs:

mod math;
pub use math::Matrix;

La dichiarazione modulo è anche neccessary per i moduli vector, matrixe complex, perché mathha bisogno di caricarli di riesportare loro. Una riesportazione di un identificatore funziona solo se hai caricato il modulo dell'identificatore. Ciò significa che per riesportare l'identificatore math::matrix::Matrixè necessario scrivere mod matrix;. Puoi farlo in matematica / mod.rs. Quindi crea il file con questo contenuto:

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

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

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

Aaae hai finito.


1 I nomi dei file sorgente di solito iniziano con una lettera minuscola in Rust. Ecco perché utilizzo matrix.rs e non Matrix.rs.

2 Java è diverso. Dichiari il percorso anche con package. È ridondante. Il percorso è già evidente dalla posizione del file di origine nel filesystem. Perché ripetere queste informazioni in una dichiarazione all'inizio del file? Ovviamente a volte è più facile dare una rapida occhiata al codice sorgente invece di scoprire la posizione del filesystem del file. Posso capire le persone che dicono che è meno confuso.


24

I puristi di Rusts probabilmente mi chiameranno eretico e odieranno questa soluzione, ma questa è molto più semplice: fai semplicemente ogni cosa nel suo file, quindi usa la macro " include! " In mod.rs:

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

In questo modo non si ottengono moduli nidificati aggiunti ed si evitano complicate regole di esportazione e riscrittura. Semplice, efficace, senza complicazioni.


1
Hai appena buttato via lo spazio dei nomi. La modifica di un file in un modo non correlato a un altro può ora interrompere altri file. Il tuo uso di "use" diventa instabile (cioè tutto è come use super::*). Non è possibile nascondere il codice da altri file (il che è importante per astrazioni sicure che non sono sicure)
Demur Rumed

12
Sì, ma è esattamente quello che volevo in quel caso: avere diversi file che si comportano come uno solo per scopi di spaziatura dei nomi. Non lo sto sostenendo per ogni caso, ma è un'utile soluzione alternativa se non si desidera affrontare il metodo "un modulo per file", per qualsiasi motivo.
hasvn

Questo è fantastico, ho una parte del mio modulo che è solo interna ma autosufficiente, e questo ha funzionato. Cercherò di far funzionare anche la soluzione del modulo appropriata, ma non è così facile.
rjh

6
non mi interessa essere chiamato eretico, la tua soluzione è conveniente!
sailfish009

21

Va bene, ho combattuto per un po 'con il mio compilatore e finalmente l'ho fatto funzionare (grazie a BurntSushi per averlo segnalato 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};
}

matematica / 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...
}

Altre strutture potrebbero essere aggiunte nello stesso modo. NOTA: compilato con 0.9, non master.


4
Nota che il tuo utilizzo mod math;in main.rscoppia il tuo mainprogramma con la tua libreria. Se vuoi che il tuo mathmodulo sia indipendente, dovrai compilarlo separatamente e collegarlo ad esso con extern crate math(come mostrato nella mia risposta). In Rust 0.9, è possibile che la sintassi sia extern mod mathinvece.
BurntSushi5

20
Sarebbe stato davvero giusto contrassegnare la risposta di BurntSushi5 come quella corretta.
IluTov

2
@NSAddict No. Per separare i moduli dai file non è necessario creare una cassa separata. È troppo ingegnerizzato.
nalply

1
Perché non è questa la risposta più votata ?? La domanda chiedeva come suddividere il progetto in pochi file, che è semplice come mostra questa risposta, non come dividerlo in casse, il che è più difficile ed è ciò che ha risposto @ BurntSushi5 (forse la domanda è stata modificata?). ..
Renato

6
La risposta di @ BurntSushi5 avrebbe dovuto essere la risposta accettata. È socialmente imbarazzante e forse significa anche fare una domanda, ottenere una risposta molto carina, quindi riassumerla come risposta separata e contrassegnare il tuo riepilogo come risposta accettata.
hasanyasin

4

Vorrei aggiungere qui come includere i file Rust quando sono profondamente nidificati. Ho la seguente struttura:

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

Come si accede sink.rso toilet.rsda main.rs?

Come altri hanno già detto, Rust non è a conoscenza dei file. Invece vede tutto come moduli e sottomoduli. Per accedere ai file all'interno della directory del bagno è necessario esportarli o spostarli in alto. Puoi farlo specificando un nome file con la directory a cui desideri accedere epub mod filename_inside_the_dir_without_rs_ext all'interno del file.

Esempio.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Crea un file chiamato bathroom.rsall'interno della homedirectory:

  2. Esporta i nomi dei file:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Crea un file chiamato home.rsaccanto amain.rs

  4. pub mod il file bathroom.rs

    // home.rs
    pub mod bathroom;
  5. Entro 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 le dichiarazioni possono essere utilizzate anche:

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

Inclusione di altri moduli (file) fratelli all'interno dei sottomoduli

Nel caso in cui desideri utilizzare sink.rsda toilet.rs, puoi chiamare il modulo specificando le parole chiave selfo super.

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

Struttura della directory finale

Finiresti con qualcosa del genere:

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

La struttura sopra funziona solo con Rust 2018 in poi. La seguente struttura di directory è valida anche per il 2018, ma è così che funzionava il 2015.

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

In cui home/mod.rsè lo stesso di ./home.rsed home/bathroom/mod.rsè lo stesso di home/bathroom.rs. Rust ha apportato questa modifica perché il compilatore si sarebbe confuso se includessi un file con lo stesso nome della directory. La versione 2018 (quella mostrata per prima) corregge quella struttura.

Vedi questo repository per ulteriori informazioni e questo video di YouTube per una spiegazione generale.

Un'ultima cosa ... evita i trattini! Usosnake_case invece.

Nota importante

È necessario a botte tutti i file verso l'alto, anche se i file profonde non sono richiesti da quelle di livello superiore.

Ciò significa che, per sink.rsscoprirli toilet.rs, avresti bisogno di barilarli usando i metodi sopratutto fino amain.rs !

In altre parole, fare pub mod sink;o use self::sink; all'interno toilet.rssarà non lavoro a meno che non hanno esposto loro tutta la strada fino a main.rs!

Pertanto, ricorda sempre di portare i tuoi file in cima!


2
... che è follemente contorto rispetto a C ++, che sta dicendo qualcosa
Joseph Garvin il

1
Migliore risposta, grazie.
etech
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.