Разделение модулей на разные файлы
До сих пор все примеры в этой главе определяли несколько модулей в одном файле. Когда модули становятся большими, вы можете захотеть переместить их определения в отдельные файлы, чтобы упростить навигацию по коду.
Например, давайте начнём с кода из листинга 7-17, в котором было несколько модулей ресторана. Мы будем извлекать модули в файлы вместо того, чтобы определять все модули в корневом модуле крейта. В нашем случае корневой модуль крейта - src/lib.rs, но это разделение также работает и с бинарными крейтами, у которых корневой модуль крейта — src/main.rs.
Сначала мы извлечём модуль front_of_house
в свой собственный файл. Удалите код внутри фигурных скобок для модуля front_of_house
, оставив только объявление mod front_of_house;
, так что теперь src/lib.rs содержит код, показанный в листинге 7-21. Обратите внимание, что этот вариант не скомпилируется, пока мы не создадим файл src/front_of_house.rs из листинге 7-22.
Файл: src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Затем поместим код, который был в фигурных скобках, в новый файл с именем src/front_of_house.rs, как показано в листинге 7-22. Компилятор знает, что нужно искать в этом файле, потому что он наткнулся в корневом модуле крейта на объявление модуля с именем front_of_house
.
Файл: src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
Обратите внимание, что вам нужно только один раз загрузить файл с помощью объявления mod
в вашем дереве модулей. Как только компилятор узнает, что файл является частью проекта (и узнает, где в дереве модулей находится код из-за того, куда вы поместили инструкцию mod
), другие файлы в вашем проекте должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе «Пути для ссылки на элемент в дереве модулей». Другими словами, mod
— это не операция «включения», которую вы могли видеть в других языках программирования.
Далее мы извлечём модуль hosting
в его собственный файл. Процесс немного отличается, потому что hosting
является дочерним модулем для front_of_house
, а не корневого модуля. Мы поместим файл для hosting
в новый каталог, который будет назван по имени его предка в дереве модулей, в данном случае это src/front_of_house/.
Чтобы начать перенос hosting
, мы меняем src/front_of_house.rs так, чтобы он содержал только объявление модуля hosting
:
Файл: src/front_of_house.rs
pub mod hosting;
Затем мы создаём каталог src/front_of_house и файл hosting.rs, в котором будут определения, сделанные в модуле hosting
:
Файл: src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
Если вместо этого мы поместим hosting.rs в каталог src, компилятор будет думать, что код в hosting.rs это модуль hosting
, объявленный в корне крейта, а не объявленный как дочерний модуль front_of_house
. Правила компилятора для проверки какие файлы содержат код каких модулей предполагают, что каталоги и файлы точно соответствуют дереву модулей.
Альтернативные пути к файлам
До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые компилятором Rust, но Rust также поддерживает и старый стиль пути к файлу. Для модуля с именем
front_of_house
, объявленного в корневом модуле крейта, компилятор будет искать код модуля в:
- src/front_of_house.rs (что мы рассматривали)
- src/front_of_house/mod.rs (старый стиль, всё ещё поддерживаемый путь)
Для модуля с именем
hosting
, который является подмодулемfront_of_house
, компилятор будет искать код модуля в:
- src/front_of_house/hosting.rs (что мы рассматривали)
- src/front_of_house/hosting/mod.rs (старый стиль, всё ещё поддерживаемый путь)
Если вы используете оба стиля для одного и того же модуля, вы получите ошибку компилятора. Использование сочетания обоих стилей для разных модулей в одном проекте разрешено, но это может сбивать с толку людей, перемещающихся по вашему проекту.
Основным недостатком стиля, в котором используются файлы с именами mod.rs, является то, что в вашем проекте может оказаться много файлов с именами mod.rs, что может привести к путанице, если вы одновременно откроете их в редакторе.
Мы перенесли код каждого модуля в отдельный файл, а дерево модулей осталось прежним. Вызовы функций в eat_at_restaurant
будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот метод позволяет перемещать модули в новые файлы по мере увеличения их размеров.
Обратите внимание, что инструкция pub use crate::front_of_house::hosting
в src/lib.rs также не изменилась, и use
не влияет на то, какие файлы компилируются как часть крейта. Ключевое слово mod
объявляет модули, и Rust ищет в файле с тем же именем, что и у модуля, код, который входит в этот модуль.
Итог
Rust позволяет разбить пакет на несколько крейтов и крейт - на модули, так что вы можете ссылаться на элементы, определённые в одном модуле, из другого модуля. Это можно делать при помощи указания абсолютных или относительных путей. Эти пути можно добавить в область видимости инструкцией use
, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Код модуля по умолчанию является приватным, но можно сделать определения общедоступными, добавив ключевое слово pub
.
В следующей главе мы рассмотрим некоторые коллекции структур данных из стандартной библиотеки, которые вы можете использовать в своём аккуратно организованном коде.