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


102

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

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Я хочу, щоб кожна структура була в одному модулі, який я б використовував у своєму головному файлі, наприклад:

use Math::Vector;

fn main() {
  // ...
}

Однак модульна система Руста (що спочатку трохи заплутано) не дає очевидного способу зробити це. Здається, він дозволяє вам мати весь модуль в одному файлі. Це нестирно? Якщо ні, то як це зробити?


1
Я інтерпретував "Я хочу мати модуль з декількома структурами в ньому, кожен у своєму власному файлі". означає, що ви хотіли, щоб кожне визначення структури було у власному файлі.
BurntSushi5

1
Це не вважатиметься сільським, хоча система модулів, безумовно, дозволяє таке структурування. Як правило, шлях модуля безпосередньо відповідає шляху файлової системи, наприклад, структура foo::bar::Bazповинна визначатися в foo/bar.rsабо foo/bar/mod.rs.
Кріс Морган

Відповіді:


111

Модульна система Руста насправді неймовірно гнучка і дозволить викрити будь-яку структуру, яку ви хочете, приховуючи, як ваш код структурований у файли.

Я думаю, що ключовим тут є використання pub use, що дозволить вам реекспортувати ідентифікатори з інших модулів. Для цього в std::ioящику Руста є прецедент, де деякі типи з підмодулів реекспортуються для використання вstd::io .

Редагувати (2018-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 також автоматично шукає в підкаталогах. Наприклад, наведений вище код буде працювати без змін із цією структурою каталогу:

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

Команди для компіляції та запуску залишаються такими ж.


Я вважаю, ви неправильно зрозуміли, що я мав на увазі під «вектором». Я говорив про вектор, як про математичну кількість , а не про структуру даних. Крім того, я не запускаю найновішу версію іржі, тому що це боляче створювати на Windows.
зірковий пейзаж

+1 не був саме тим, що мені потрібно, але вказав на мене в правильному напрямку.
зірковий пейзаж

@EpicPineapple Дійсно! І Vec може бути використаний для представлення таких векторів. (Для більших N, звичайно.)
BurntSushi5

1
@EpicPineapple Чи можете ви пояснити, що моя відповідь пропущена, щоб я міг її оновити? Я намагаюся бачити різницю між вашою відповіддю та моєю, крім використання math::Vec2замість цього math::vector::Vec2. (тобто те саме поняття, але на один модуль глибше.)
BurntSushi5

1
Я не бачу цих критеріїв у вашому питанні. Наскільки я бачу, я відповів на поставлене запитання. (Що насправді запитувало, як розділити модулі від файлів.) Вибачте, що це не працює на Rust 0.9, але це стосується території використання нестабільної мови.
BurntSushi5

38

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

  1. Вихідний файл - це лише власний модуль (крім спеціальних файлів main.rs, lib.rs та mod.rs).
  2. Каталог - лише компонент шляху модуля.
  3. Файл mod.rs - це просто модуль каталогу.

Файл matrix.rs 1 в математиці каталогів є лише модулем math::matrix. Це легко. Що ви бачите у своїй файловій системі, ви також знаходите у своєму вихідному коді. Це відповідність один до одного шляхів до файлів та шляхів модулів 2 .

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

pub use self::math::Matrix;

Є ще один крок, щоб налагодити це. Для завантаження модуля Іржа потрібна декларація модуля. Додати 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 Java різні. Ви також оголошуєте шлях packageтакож. Це зайве. Шлях вже видно з розташування вихідного файлу у файловій системі. Навіщо повторювати цю інформацію в декларації у верхній частині файлу? Звичайно, іноді простіше швидко переглянути вихідний код, а не дізнатися місце розташування файлової системи. Я можу зрозуміти людей, які кажуть, що це менш заплутано.


23

Пуристи-іржі, ймовірно, називатимуть мене єретиком і ненавиджу це рішення, але це набагато простіше: просто робіть кожну річ у своєму власному файлі, а потім використовуйте макрос " включити! " В mod.rs:

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

Таким чином ви не отримуєте доданих вкладених модулів і уникаєте складних правил експорту та переписування. Простий, ефективний, без суєти.


1
Ви просто викинули інтервал імен. Зміна одного файлу, не пов’язаного з іншим, тепер може порушувати інші файли. Ваше використання "використання" стає пропускним (тобто все як use super::*). Ви не можете приховати код від інших файлів (що важливо для небезпечних безпечних абстракцій)
Demur Rumed

11
Так, але саме цього я хотів у такому випадку: мати кілька файлів, які ведуть себе як один лише для цілей простору імен. Я не виступаю за це для кожного випадку, але це корисне рішення, якщо ви не хочете мати справу з методом "один модуль на файл" з будь-якої причини.
hasvn

Це чудово, у мене є частина мого модуля, яка є лише внутрішньою, але самодостатньою, і це зробило трюк. Я спробую зробити так, щоб правильне модульне рішення теж працювало, але це не так близько, як близько.
rjh

5
Мені все одно, що мене називають єретиком, ваше рішення зручне!
sailfish009

21

Гаразд, бився з моїм компілятором деякий час і, нарешті, змусив його працювати (спасибі 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, не головним.


4
Зверніть увагу , що використання mod math;в main.rsпарі вашої mainпрограми з Вашою бібліотекою. Якщо ви хочете, щоб ваш mathмодуль був незалежним, вам потрібно буде скласти його окремо і пов’язати з ним extern crate math(як показано у моїй відповіді). У Rust 0.9 можливо, що extern mod mathзамість цього є синтаксис .
BurntSushi5

20
Дійсно було б справедливо позначити відповідь BurntSushi5 як правильну.
IluTov

2
@NSAddict No. Для розлучення модулів з файлів не потрібно створювати окремий ящик. Це надмірно розроблено.
nalply

1
Чому це не найголовніша відповідь ?? Питання задавало питання, як розбити проект на кілька файлів, що так само просто, як показує ця відповідь, а не як розділити його на ящики, що складніше і це те, на що відповів @ BurntSushi5 (можливо, питання було відредаговано?). ..
Ренато

6
@ Відповідь BurntSushi5 повинна була бути прийнятою відповіддю. Це соціально незручно і, можливо, навіть означає поставити запитання, отримати дуже гарну відповідь, а потім узагальнити його як окрему відповідь і позначити своє резюме як прийняту відповідь.
hasanyasin

4

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

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

Як ви отримуєте доступ sink.rsчи toilet.rsз якого main.rs?

Як уже згадували інші, Руст не знає файлів. Натомість він розглядає все як модулі та підмодулі. Щоб отримати доступ до файлів у каталозі ванної кімнати, потрібно експортувати їх або запустити вгору. Виконуєте це, вказавши ім'я файлу з каталогом, до якого ви хочете отримати доступ, і 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 файл ванної кімнати

    // 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. Іржа внесла цю зміну, оскільки компілятор заплутався б, якщо ви включили файл з тим же ім'ям, що і каталог. Версія 2018 року (перша, показана першою) виправляє цю структуру.

Дивіться це репо отримати докладніші відомості, для отримання додаткової інформації та це відео YouTube .

Останнє ... уникати дефісів! Використовуйте snake_caseзамість цього.

Важлива примітка

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

Це означає, що для того, sink.rsщоб виявити toilet.rs, вам доведеться їх заборонити, використовуючи методи, перш за все, доmain.rs !

Іншими словами, робити pub mod sink;або use self::sink; всередині неtoilet.rs вийде, якщо ви не виставляєте їх до кінця !main.rs

Тому завжди пам’ятайте, що барель файлів доверху!


2
... це шалено заплутане порівняно з C ++, що щось говорить
Джозеф Гарвін

1
Найкраща відповідь, спасибі
etech
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.