Разделить модуль на несколько файлов
Я хочу иметь модуль с несколькими структурами, каждый в своем собственном файле. Используя модуль Math
в качестве примера:
Math/
Vector.rs
Matrix.rs
Complex.rs
Я хочу, чтобы каждая структура была в том же модуле, который я использовал бы из моего основного файла, например так:
use Math::Vector;
fn main() {
// ...
}
Однако модульная система Rust (с самого начала немного запутанная) не предоставляет очевидного способа сделать это. Кажется, он позволяет вам хранить весь модуль в одном файле. Это не простовато? Если нет, как мне это сделать?
Ответы
Ответ 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");
Таким образом, вы не получаете добавленных вложенных модулей и избегаете сложных правил экспорта и перезаписи.
Просто, эффективно, без суеты.