Разделить модуль на несколько файлов

Я хочу иметь модуль с несколькими структурами, каждый в своем собственном файле. Используя Math Модуль в качестве примера:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Я хочу, чтобы каждая структура была в том же модуле, который я использовал бы из моего основного файла, например так:

use Math::Vector;

fn main() {
  // ...
}

Однако модульная система Rust (с самого начала немного запутанная) не дает очевидного способа сделать это. Кажется, он позволяет вам хранить весь модуль в одном файле. Это не простовато? Если нет, как мне это сделать?

7 ответов

Решение

Хорошо, некоторое время боролся с моим компилятором и, наконец, заставил его работать (спасибо BurntSushi за указание 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};
}

математика /mod.rs:

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

математика /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...
}

Другие структуры могут быть добавлены таким же образом. ПРИМЕЧАНИЕ: составлено с 0,9, а не мастер.

Система модулей Rust на самом деле невероятно гибкая и позволит вам показать любую структуру, какую вы пожелаете, скрывая, как ваш код структурирован в файлах.

Я думаю, что ключ здесь заключается в том, чтобы использовать pub use, что позволит вам реэкспортировать идентификаторы из других модулей. У Руста есть прецедент std::io ящик, где некоторые типы из подмодулей реэкспортируются для использования в std::io,

Чтобы адаптировать ваш пример, мы могли бы начать с этой структуры каталогов:

src/
  lib.rs
  vector.rs
main.rs

Вот твой main.rs:

extern crate math;

use math::vector;

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

И ваш src/lib.rs:

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

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

И наконец, 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![] }
        }
    }
}

И здесь происходит волшебство. Мы определили подмодуль math::vector::vector_a который имеет некоторую реализацию специального вида вектора. Но мы не хотим, чтобы клиенты вашей библиотеки заботились о том, чтобы vector_a суб-модуль. Вместо этого мы хотели бы сделать его доступным в math::vector модуль. Это сделано с pub use self::vector_a::VectorA, который реэкспортирует vector_a::VectorA идентификатор в текущем модуле.

Но вы спросили, как это сделать, чтобы вы могли поместить свои специальные векторные реализации в разные файлы. Это то, что mod vector_b; линия делает. Он инструктирует компилятор Rust искать vector_b.rs файл для реализации этого модуля. И конечно же, вот наш src/vector_b.rs файл:

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

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

С точки зрения клиента, тот факт, что VectorA а также VectorB Определяются в двух разных модулях в двух разных файлах полностью непрозрачно.

Если вы находитесь в том же каталоге, что и main.rs, вы должны быть в состоянии запустить его с:

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

В общем, глава "Ящики и модули" в книге Rust довольно хороша. Есть много примеров.

Наконец, компилятор Rust также автоматически ищет в подкаталогах. Например, приведенный выше код будет работать без изменений с этой структурой каталогов:

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

Команды для компиляции и запуска остаются такими же.

Правила модуля Rust:

  1. Исходный файл - это просто его собственный модуль (за исключением специальных файлов main.rs, lib.rs и mod.rs).
  2. Каталог - это просто компонент пути модуля.
  3. Файл mod.rs - это просто модуль каталога.

Файл matrix.rs1 в каталоге math является просто модулем math::matrix, Это просто. То, что вы видите в своей файловой системе, вы также найдете в своем исходном коде. Это взаимно-однозначное соответствие путей к файлам и модулям2.

Таким образом, вы можете импортировать структуру Matrix с use math::matrix::Matrix, потому что структура находится внутри файла matrix.rs в каталоге math. Не счастлив? Вы бы предпочли use math::Matrix; очень много, не так ли? Возможно. Реэкспорт идентификатора math::matrix::Matrix в математике / мод.рс с:

pub use self::math::Matrix;

Есть еще один шаг, чтобы заставить это работать. Rust требуется объявление модуля для загрузки модуля. Добавить mod math; в main.rs. Если вы этого не сделаете, вы получите сообщение об ошибке от компилятора при импорте следующим образом:

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

Намек вводит в заблуждение здесь. Нет необходимости в дополнительных ящиках, за исключением того, что вы действительно намереваетесь написать отдельную библиотеку.

Добавьте это вверху main.rs:

mod math;
pub use math::Matrix;

Объявление модуля также необходимо для подмодулей vector, matrix а также complex, так как math необходимо загрузить их, чтобы реэкспортировать их. Реэкспорт идентификатора работает, только если вы загрузили модуль идентификатора. Это означает, что реэкспорт идентификатора math::matrix::Matrix тебе нужно написать mod matrix;, Вы можете сделать это в math / mod.rs. Поэтому создайте файл с таким содержанием:

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

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

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

Ааа а ты молодец


1 Имена исходных файлов обычно начинаются со строчной буквы в Rust. Вот почему я использую matrix.rs, а не Matrix.rs.

2 Ява отличается. Вы объявляете путь с package, тоже. Это избыточно. Путь уже очевиден из местоположения исходного файла в файловой системе. Зачем повторять эту информацию в объявлении в верхней части файла? Конечно, иногда проще быстро взглянуть на исходный код, чем узнать, где находится файл в файловой системе. Я могу понять людей, которые говорят, что это менее запутанно.

Пуристы Rusts, вероятно, назовут меня еретиком и возненавидят это решение, но это гораздо проще: просто делайте каждую вещь в своем собственном файле, а затем используйте макрос " include!" В mod.rs:

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

Таким образом, вы не получаете никаких вложенных модулей и избегаете сложных правил экспорта и перезаписи. Просто, эффективно, без суеты.

Я хотел бы добавить сюда, как вы включаете файлы Rust, когда они глубоко вложены. У меня такая структура:

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

Как получить доступ sink.rs или toilet.rs из main.rs?

Как уже упоминалось, Rust не знает файлов. Вместо этого он видит все как модули и подмодули. Чтобы получить доступ к файлам в каталоге ванной комнаты, вам нужно экспортировать их или поставить наверх. Вы делаете это, указывая имя файла с каталогом, к которому вы хотите получить доступ, иpub mod filename_inside_the_dir_without_rs_ext внутри файла.

Пример.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Создайте файл с именем bathroom.rs внутри home каталог:

  2. Экспортируйте имена файлов:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
  3. Создайте файл с именем home.rs следующий на main.rs

  4. pub mod файл bathroom.rs

    // home.rs
    pub mod bathroom;
    
  5. В 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 также могут использоваться операторы:

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

Включение других родственных модулей (файлов) в подмодули

Если вы хотите использовать sink.rs из toilet.rs, вы можете вызвать модуль, указав self или super ключевые слова.

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

Окончательная структура каталогов

Вы получите что-то вроде этого:

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

Приведенная выше структура работает только с Rust 2018 и новее. Следующая структура каталогов также действительна для 2018 года, но именно так в 2015 году работал.

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

В котором home/mod.rs такой же как ./home.rs а также home/bathroom/mod.rs такой же как home/bathroom.rs. Rust внес это изменение, потому что компилятор запутается, если вы включите файл с тем же именем, что и каталог. Версия 2018 года (показанная первой) исправляет эту структуру.

См. Это репо для получения дополнительной информации и это видео на YouTube для общего объяснения.

И последнее... избегайте дефисов! Использоватьsnake_case вместо.

Важная заметка

Вы должны направить все файлы вверх, даже если глубокие файлы не требуются для файлов верхнего уровня.

Это означает, что для sink.rs открывать toilet.rs, вам нужно будет использовать их, используя описанные выше методы вплоть до main.rs!

Другими словами, делая pub mod sink; или use self::sink; внутри toilet.rsне будет работать, если вы не раскрыли их полностью доmain.rs!

Поэтому всегда не забывайте ставить файлы наверх!

Еще один метод экспорта модуля, который я взял с Github.

      mod foo {
    //! inner docstring comment 1
    //! inner docstring comment 2

    mod a;
    mod b;

    pub use a::*;
    pub use b::*;
}

Корректировка примера каталога вопроса и имен файлов в соответствии с соглашениями об именах Rust:

      main.rs
math.rs
math/
  vector.rs
  matrix.rs
  complex.rs

Обязательно экспортируйте общедоступные символы (типы, функции и т. д.) в каждый из файлов в каталоге, поставив перед ними ключевое словоpub.

Определятьmath.rs:

      mod vector;
pub use vector::*;

mod matrix;
pub use matrix::*;

mod complex;
pub use complex::*;

В приведенном выше файле подмодули остаются закрытыми, но общедоступные символы подмодулей экспортируются из модуля.math. Это эффективно сглаживает структуру модуля.

Использоватьmath::Vectorвmain.rs:

      mod math;

use crate::math::Vector;

fn main() {
  // ...
}
Другие вопросы по тегам