Разделить модуль на несколько файлов
Я хочу иметь модуль с несколькими структурами, каждый в своем собственном файле. Используя 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:
- Исходный файл - это просто его собственный модуль (за исключением специальных файлов main.rs, lib.rs и mod.rs).
- Каталог - это просто компонент пути модуля.
- Файл 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.")
}
Создайте файл с именем
bathroom.rs
внутриhome
каталог:Экспортируйте имена файлов:
// bathroom.rs pub mod sink; pub mod toilet;
Создайте файл с именем
home.rs
следующий наmain.rs
pub mod
файл bathroom.rs// home.rs pub mod bathroom;
В
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() {
// ...
}