Разделение модулей на файлы
До сих пор во всех примерах этой главы несколько модулей определялись в одном файле. Когда модули разрастаются, их определения удобно вынести в отдельные файлы, чтобы в коде было проще ориентироваться.
Возьмём для примера код из листинга 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.
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
front_of_house, тело которого будет в src/front_of_house.rsЗатем перенесите код, который был в фигурных скобках, в новый файл
src/front_of_house.rs, как в листинге 7-22. Компилятор знает, что искать
нужно в этом файле, потому что в корне крейта он встретил объявление модуля с
именем front_of_house.
pub mod hosting {
pub fn add_to_waitlist() {}
}
front_of_house в файле src/front_of_house.rsОбратите внимание: загружать файл объявлением mod нужно лишь один раз во
всём дереве модулей. Как только компилятор знает, что файл — часть проекта (и
знает, где в дереве модулей находится его код, — по тому, где стоит
инструкция mod), остальные файлы проекта должны обращаться к коду
загруженного файла по пути к месту его объявления, как описано в разделе
«Пути к элементам в дереве модулей». Иными словами,
mod — это не операция «include», которая может быть вам знакома по другим
языкам программирования.
Теперь вынесем в отдельный файл модуль hosting. Тут всё немного иначе:
hosting — дочерний модуль front_of_house, а не корневого модуля. Файл для
hosting положим в новую папку, названную по его предкам в дереве модулей, —
в данном случае src/front_of_house.
Чтобы начать перенос hosting, оставим в src/front_of_house.rs только
объявление модуля hosting:
pub mod hosting;
Затем создадим папку src/front_of_house и в ней файл hosting.rs с
определениями из модуля hosting:
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.
В следующей главе мы рассмотрим структуры данных — коллекции из стандартной библиотеки, которыми можно пользоваться в вашем аккуратно организованном коде.