Подтвердить что ты не робот

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

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

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

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

use Math::Vector;

fn main() {
  // ...
}

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Изменить (2019-08-25): следующая часть ответа была написана довольно давно. Он объясняет, как настроить такую модульную структуру только с помощью rustc. Сегодня обычно используют Cargo для большинства случаев использования. Хотя следующее все еще действует, некоторые его части (например, #![crate_type = ...]) могут показаться странными. Это не рекомендуемое решение.

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

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

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

Ответ 3

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

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

Файл matrix.rs 1 в каталоге math - это просто модуль math::matrix. Это легко. Что вы видите в своей файловой системе, которую вы также найдете в своем исходном коде. Это взаимно однозначное соответствие путей файлов и путей модулей 2.

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

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;. Вы можете сделать это в математике /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 Java разный. Вы также указываете путь с помощью package. Это избыточно. Путь уже проявляется в местоположении исходного файла в файловой системе. Зачем повторять эту информацию в объявлении в верхней части файла? Конечно, иногда бывает проще быстро взглянуть на исходный код, а не обнаруживать расположение файловой системы файла. Я могу понять людей, которые считают это менее запутанным.

Ответ 4

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

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

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