Модули: область видимости и приватность
В этом разделе мы поговорим о модулях и других частях системы модулей: о
путях, которые позволяют называть элементы; о ключевом слове use, которое
подключает путь в область видимости; и о ключевом слове pub, которое делает
элементы публичными. Ещё мы обсудим ключевое слово as, внешние пакеты и
оператор * (glob).
Шпаргалка по модулям
Прежде чем углубляться в модули и пути, приведём краткую справку: как
компилятор обрабатывает модули, пути и ключевые слова use и pub и как
большинство разработчиков организуют код. На протяжении главы мы разберём
каждое из этих правил на примерах, а сюда удобно возвращаться, чтобы
освежить в памяти, как работают модули.
- Начало — корень крейта. Компилируя крейт, компилятор сначала ищет код в файле корня крейта (обычно это src/lib.rs для библиотечного крейта и src/main.rs для бинарного).
- Объявление модулей. В файле корня крейта можно объявлять новые модули.
Допустим, вы объявили модуль «garden» (сад) строкой
mod garden;. Компилятор будет искать код модуля в следующих местах:- прямо в этом файле, в фигурных скобках, которые стоят вместо точки с
запятой после
mod garden; - в файле src/garden.rs;
- в файле src/garden/mod.rs.
- прямо в этом файле, в фигурных скобках, которые стоят вместо точки с
запятой после
- Объявление подмодулей. В любом файле, кроме корня крейта, можно
объявлять подмодули. Например, в src/garden.rs можно объявить
mod vegetables;(овощи). Компилятор будет искать код подмодуля в папке с именем родительского модуля, в следующих местах:- прямо в этом файле, сразу после
mod vegetables, в фигурных скобках вместо точки с запятой; - в файле src/garden/vegetables.rs;
- в файле src/garden/vegetables/mod.rs.
- прямо в этом файле, сразу после
- Пути к коду в модулях. Когда модуль стал частью крейта, на его код
можно ссылаться из любого места этого крейта (если позволяют правила
приватности) — по пути к этому коду. Например, тип
Asparagus(спаржа) из модуля овощей в саду будет доступен по путиcrate::garden::vegetables::Asparagus. - Приватное и публичное. По умолчанию код модуля скрыт от родительских
модулей. Чтобы сделать модуль публичным, объявите его через
pub modвместоmod. Чтобы сделать публичными и элементы внутри публичного модуля, поставьтеpubперед их объявлениями. - Ключевое слово
use. Внутри области видимостиuseсоздаёт ярлыки для элементов, чтобы не повторять длинные пути. В любой области видимости, где можно сослаться наcrate::garden::vegetables::Asparagus, можно создать ярлыкuse crate::garden::vegetables::Asparagus;— и дальше в этой области видимости достаточно писать простоAsparagus.
Создадим бинарный крейт backyard (задний двор) и посмотрим на эти правила в
деле. В папке крейта, которая тоже называется backyard, такие файлы и
папки:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
Корень крейта здесь — src/main.rs, и в нём вот что:
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {plant:?}!");
}
Строка pub mod garden; говорит компилятору подключить код из src/garden.rs,
а там вот что:
pub mod vegetables;
А pub mod vegetables; здесь означает, что подключается ещё и код из
src/garden/vegetables.rs:
#[derive(Debug)]
pub struct Asparagus {}
А теперь разберём эти правила подробно и посмотрим на них в действии!
Группировка связанного кода в модули
Модули позволяют организовать код внутри крейта так, чтобы его было удобно читать и повторно использовать. Кроме того, модули управляют приватностью элементов: по умолчанию код внутри модуля приватный. Приватные элементы — это внутренние детали реализации, недоступные снаружи. Модули и элементы в них можно сделать публичными — тогда внешний код сможет ими пользоваться и зависеть от них.
Для примера напишем библиотечный крейт, моделирующий работу ресторана. Мы определим сигнатуры функций, но оставим их тела пустыми, чтобы сосредоточиться на организации кода, а не на реализации ресторана.
В ресторанном деле заведение делят на зал (front of house) и служебную часть (back of house). Зал — это место, где находятся посетители: администратор рассаживает гостей, официанты принимают заказы и оплату, бармены готовят напитки. Служебная часть — это кухня, где работают шеф-повар и повара, мойщики посуды наводят чистоту, а менеджеры занимаются административной работой.
Чтобы устроить крейт так же, разложим его функции по вложенным модулям.
Создайте новую библиотеку restaurant командой
cargo new restaurant --lib. Затем введите в src/lib.rs код из
листинга 7-1 — он определяет несколько модулей и сигнатур функций. Это
«зал».
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
front_of_house, содержащий другие модули, в которых находятся функцииМодуль определяется ключевым словом mod, за которым идёт имя модуля (здесь
— front_of_house). Тело модуля пишется в фигурных скобках. Внутри модуля
можно размещать другие модули — как здесь модули hosting (встреча гостей) и
serving (обслуживание). Ещё в модулях могут быть определения других
элементов: структур, перечислений, констант, трейтов и, как в листинге 7-1,
функций.
Модули позволяют собрать связанные определения вместе и назвать то, что их объединяет. Программистам, которые пользуются этим кодом, проще ориентироваться по группам, чем читать все определения подряд, — так легче найти то, что им нужно. А программисты, которые добавляют в код новую функциональность, будут знать, куда её поместить, чтобы программа оставалась упорядоченной.
Выше мы говорили, что src/main.rs и src/lib.rs называются корнями
крейта. Название объясняется так: содержимое любого из этих двух файлов
образует модуль с именем crate, который находится в корне структуры модулей
крейта — так называемого дерева модулей (module tree).
В листинге 7-2 показано дерево модулей для структуры из листинга 7-1.
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Дерево показывает, какие модули вложены в другие: например, hosting вложен в
front_of_house. Видно и то, что некоторые модули — соседи (siblings), то
есть определены в одном и том же модуле: hosting и serving — соседи внутри
front_of_house. Если модуль A находится внутри модуля B, говорят, что модуль
A — потомок (child) модуля B, а B — родитель (parent) модуля A.
Обратите внимание: корнем всего дерева модулей служит неявный модуль с именем
crate.
Дерево модулей может напомнить вам дерево папок в файловой системе — и это очень удачное сравнение! Как папки в файловой системе, модули служат для организации кода. И точно так же, как файлы в папках, модули нужно уметь находить.