Dividir un módulo en varios archivos

102

Quiero tener un módulo con múltiples estructuras, cada una en su propio archivo. Usando un Mathmódulo como ejemplo:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Quiero que cada estructura esté en el mismo módulo, que usaría desde mi archivo principal, así:

use Math::Vector;

fn main() {
  // ...
}

Sin embargo, el sistema de módulos de Rust (que es un poco confuso para empezar) no proporciona una forma obvia de hacer esto. Parece que solo le permite tener todo su módulo en un archivo. ¿Es esto poco rústico? Si no es así, ¿cómo hago esto?

paisaje estelar
fuente
1
Interpreté "Quiero tener un módulo con múltiples estructuras, cada una en su propio archivo". para significar que deseaba cada definición de estructura en su propio archivo.
BurntSushi5
1
Esto no se consideraría rústico, aunque el sistema modular ciertamente permite tal estructuración. Por lo general, es preferible que la ruta de un módulo se corresponda directamente con una ruta del sistema de archivos, por ejemplo, la estructura foo::bar::Bazdebe definirse en foo/bar.rso foo/bar/mod.rs.
Chris Morgan

Respuestas:

111

El sistema de módulos de Rust es realmente increíblemente flexible y le permitirá exponer cualquier tipo de estructura que desee mientras oculta cómo se estructura su código en archivos.

Creo que la clave aquí es hacer uso de pub use, lo que le permitirá reexportar identificadores de otros módulos. Hay un precedente para esto en la std::iocaja de Rust, donde algunos tipos de submódulos se reexportan para su uso enstd::io .

Editar (2019-08-25): la siguiente parte de la respuesta se escribió hace bastante tiempo. Explica cómo configurar una estructura de módulo de este tipo con rustcsolo. Hoy en día, normalmente se usa Cargo para la mayoría de los casos de uso. Si bien lo siguiente sigue siendo válido, algunas partes (por ejemplo #![crate_type = ...]) pueden parecer extrañas. Ésta no es la solución recomendada.

Para adaptar su ejemplo, podríamos comenzar con esta estructura de directorio:

src/
  lib.rs
  vector.rs
main.rs

Aquí está tu main.rs:

extern crate math;

use math::vector;

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

Y tu src/lib.rs:

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

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

Y finalmente 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![] }
        }
    }
}

Y aquí es donde ocurre la magia. Hemos definido un submódulo math::vector::vector_aque tiene alguna implementación de un tipo especial de vector. Pero no queremos que a los clientes de su biblioteca les importe que haya un vector_asubmódulo. En cambio, nos gustaría que esté disponible en el math::vectormódulo. Esto se hace con pub use self::vector_a::VectorA, que reexporta el vector_a::VectorAidentificador en el módulo actual.

Pero preguntaste cómo hacer esto para poder poner tus implementaciones vectoriales especiales en diferentes archivos. Esto es lo que hace la mod vector_b;línea. Indica al compilador de Rust que busque un vector_b.rsarchivo para la implementación de ese módulo. Y efectivamente, aquí está nuestro src/vector_b.rsarchivo:

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

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

Desde la perspectiva del cliente, el hecho de que VectorAy VectorBestén definidos en dos módulos diferentes en dos archivos diferentes es completamente opaco.

Si está en el mismo directorio que main.rs, debería poder ejecutarlo con:

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

En general, el capítulo "Cajas y módulos" del libro de Rust es bastante bueno. Hay muchos ejemplos.

Finalmente, el compilador de Rust también busca en subdirectorios automáticamente. Por ejemplo, el código anterior funcionará sin cambios con esta estructura de directorio:

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

Los comandos para compilar y ejecutar siguen siendo los mismos.

BurntSushi5
fuente
Creo que entendiste mal lo que quise decir con "vector". Estaba hablando de vector como cantidad matemática , no como estructura de datos. Además, no estoy ejecutando la última versión de rust, porque es un poco complicado construirlo en Windows.
starscape
+1 no era exactamente lo que necesitaba, pero me indicó la dirección correcta.
paisaje estelar
@EpicPineapple ¡De hecho! Y se puede usar un Vec para representar tales vectores. (Para N más grande, por supuesto.)
BurntSushi5
1
@EpicPineapple ¿Podría explicar qué se ha perdido en mi respuesta para que pueda actualizarla? Estoy luchando por ver la diferencia entre tu respuesta y la mía además de usar en math::Vec2lugar de math::vector::Vec2. (es decir, el mismo concepto pero un módulo más profundo.)
BurntSushi5
1
No veo ese criterio en su pregunta. Por lo que puedo ver, he respondido a la pregunta formulada. (Lo que en realidad era preguntar cómo separar módulos de archivos). Lamento que no funcione en Rust 0.9, pero eso viene con el territorio de usar un lenguaje inestable.
BurntSushi5
39

Las reglas del módulo Rust son:

  1. Un archivo fuente es solo su propio módulo (excepto los archivos especiales main.rs, lib.rs y mod.rs).
  2. Un directorio es solo un componente de ruta de módulo.
  3. El archivo mod.rs es solo el módulo del directorio.

El archivo matrix.rs 1 en el directorio math es solo el módulo math::matrix. Es fácil. Lo que ves en tu sistema de archivos también lo encontrarás en tu código fuente. Esta es una correspondencia uno a uno de rutas de archivo y rutas de módulo 2 .

Entonces puede importar una estructura Matrixcon use math::matrix::Matrix, porque la estructura está dentro del archivo matrix.rs en un directorio math. ¿No feliz? Preferirías use math::Matrix;mucho en su lugar, ¿no? Es posible. Vuelva a exportar el identificador math::matrix::Matrixen math / mod.rs con:

pub use self::math::Matrix;

Hay otro paso para que esto funcione. Rust necesita una declaración de módulo para cargar el módulo. Agregue un mod math;en main.rs. Si no lo hace, recibirá un mensaje de error del compilador al importar así:

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

La pista es engañosa aquí. No hay necesidad de cajas adicionales, excepto, por supuesto, que realmente tiene la intención de escribir una biblioteca separada.

Agregue esto en la parte superior de main.rs:

mod math;
pub use math::Matrix;

La declaración módulo también es necesaria para utilizar los submódulos vector, matrixy complex, debido a mathlas necesidades de las cargan a re-exportarlos. Una reexportación de un identificador solo funciona si ha cargado el módulo del identificador. Esto significa que para volver a exportar el identificador math::matrix::Matrixque necesita escribir mod matrix;. Puede hacer esto en math / mod.rs. Por lo tanto, cree el archivo con este contenido:

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

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

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

Aaa y ya está.


1 Los nombres de los archivos de origen suelen comenzar con una letra minúscula en Rust. Por eso uso matrix.rs y no Matrix.rs.

2 Java es diferente. También declaras el camino con package. Es redundante. La ruta ya es evidente desde la ubicación del archivo de origen en el sistema de archivos. ¿Por qué repetir esta información en una declaración en la parte superior del archivo? Por supuesto, a veces es más fácil echar un vistazo rápido al código fuente en lugar de averiguar la ubicación del archivo en el sistema de archivos. Puedo entender a las personas que dicen que es menos confuso.

nalply
fuente
24

Los puristas de Rusts probablemente me llamarán hereje y odiarán esta solución, pero esto es mucho más simple: simplemente haga cada cosa en su propio archivo, luego use la macro " include! " En mod.rs:

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

De esa manera, no obtendrá módulos anidados agregados y evitará complicadas reglas de exportación y reescritura. Simple, efectivo, sin complicaciones.

hasvn
fuente
1
Acabas de descartar el espacio de nombres. Cambiar un archivo de una manera no relacionada con otro ahora puede romper otros archivos. Su uso de 'uso' se vuelve fluido (es decir, todo es como use super::*). No puede ocultar el código de otros archivos (lo cual es importante para el uso inseguro de abstracciones seguras)
Demur Rumed
12
Sí, pero eso es exactamente lo que quería en ese caso: tener varios archivos que se comporten como uno solo para fines de espacio de nombres. No estoy defendiendo esto para todos los casos, pero es una solución alternativa útil si no desea tratar con el método "un módulo por archivo", por cualquier motivo.
Hasvn
Esto es genial, tengo una parte de mi módulo que es solo interna pero autónoma, y ​​esto funcionó. Intentaré que la solución de módulo adecuada funcione también, pero no es tan fácil.
rjh
6
No me importa que me llamen hereje, ¡su solución es conveniente!
sailfish009
21

Muy bien, luché con mi compilador por un tiempo y finalmente lo hice funcionar (gracias a BurntSushi por señalarlo 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};
}

matemáticas / mod.rs:

pub use self::vector::Vec2;
mod vector;

matemáticas / 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...
}

Se podrían agregar otras estructuras de la misma manera. NOTA: compilado con 0.9, no master.

paisaje estelar
fuente
4
Tenga en cuenta que su uso mod math;en main.rsparejas de su mainprograma con su biblioteca. Si desea que su mathmódulo sea independiente, deberá compilarlo por separado y vincularlo con extern crate math(como se muestra en mi respuesta). En Rust 0.9, es posible que la sintaxis sea extern mod math.
BurntSushi5
20
Realmente hubiera sido justo marcar la respuesta de BurntSushi5 como la correcta.
IluTov
2
@NSAddict No. Para separar módulos de archivos, no es necesario crear una caja separada. Está sobre-diseñado.
finalmente
1
¿Por qué no es esta la respuesta más votada? La pregunta era cómo dividir el proyecto en unos pocos archivos, que es tan simple como muestra esta respuesta, no cómo dividirlo en cajas, que es más difícil y es lo que respondió @ BurntSushi5 (¿tal vez la pregunta fue editada?). ..
Renato
6
La respuesta de @ BurntSushi5 debería haber sido la respuesta aceptada. Es socialmente incómodo y tal vez incluso signifique hacer una pregunta, obtener una respuesta muy agradable, luego resumirla como una respuesta separada y marcar su resumen como la respuesta aceptada.
hasanyasin
4

Me gustaría agregar aquí cómo se incluyen los archivos Rust cuando están profundamente anidados. Tengo la siguiente estructura:

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

¿Cómo accedes sink.rso toilet.rsdesde main.rs?

Como han mencionado otros, Rust no tiene conocimiento de archivos. En cambio, ve todo como módulos y submódulos. Para acceder a los archivos dentro del directorio del baño, debe exportarlos o colocarlos en la parte superior. Para ello, especifique un nombre de archivo con el directorio al que desea acceder y pub mod filename_inside_the_dir_without_rs_extdentro del archivo.

Ejemplo.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Cree un archivo llamado bathroom.rsdentro del homedirectorio:

  2. Exportar los nombres de archivo:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Cree un archivo llamado home.rsjunto amain.rs

  4. pub mod el archivo bathroom.rs

    // home.rs
    pub mod bathroom;
  5. Dentro 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 Las declaraciones también se pueden utilizar:

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

Incluir otros módulos hermanos (archivos) dentro de los submódulos

En el caso de que desee utilizar sink.rsdesde toilet.rs, puede llamar al módulo especificando las palabras clave selfo super.

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

Estructura de directorio final

Terminarías con algo como esto:

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

La estructura anterior solo funciona con Rust 2018 en adelante. La siguiente estructura de directorios también es válida para 2018, pero así es como solía funcionar 2015.

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

En que home/mod.rses igual que ./home.rsy home/bathroom/mod.rses igual que home/bathroom.rs. Rust hizo este cambio porque el compilador se confundiría si incluyese un archivo con el mismo nombre que el directorio. La versión 2018 (la que se muestra primero) corrige esa estructura.

Consulte este repositorio para obtener más información y este video de YouTube para obtener una explicación general.

Una última cosa ... ¡evita los guiones! Úselo en su snake_caselugar.

Nota IMPORTANTE

Usted debe barril todos los archivos en la parte superior, incluso si los archivos profundos no son requeridos por los de nivel superior.

Esto significa que, para sink.rsdescubrirlo toilet.rs, deberías utilizar los métodos anteriores hasta el final main.rs.

En otras palabras, hacer pub mod sink;o use self::sink; en el interior toilet.rsse no trabajo a menos que haya expuesto a todo el camino hasta main.rs!

Por lo tanto, recuerde siempre llevar sus archivos a la cima.

José A
fuente
2
... eso es increíblemente complicado en comparación con C ++, que es decir algo
Joseph Garvin
1
Mejor respuesta, gracias.
etech