Использование модулей для управления областью видимости и приватностью
В этом разделе мы поговорим о модулях и других частях системы модулей, а именно: о путях, которые позволяют именовать элементы; о ключевом слове use
, которое подключает путь к области видимости; о ключевом слове pub
, которое делает элементы общедоступными. Мы также обсудим ключевое слово as
, внешние пакеты и оператор *
.
Шпаргалка по модулям
Перед тем как вдаваться в детали работы с модулями и путями, мы дадим краткий обзор того, как модули, пути и ключевые слова use
и pub
работают в компиляторе, и как большинство разработчиков организуют свой код. В этой главе мы рассмотрим всё это на примерах, а пока что у нас есть удобный момент, чтобы дать выжимку о том, как работают крейты.
- Старт с корня крейта. В начале компиляции крейта, компилятор ищет в корне крейта (обычно это src/lib.rs для библиотечного крейта или src/main.rs для бинарного крейта) код для компиляции.
- Объявление модулей. В файле корня крейта вы можете объявлять новые модули. Скажем, вы объявляете модуль "garden" с помощью
mod garden;
. Компилятор будет искать код модуля в следующих местах:- В этом же файле между фигурных скобок, которые заменяют точку с запятой после
mod garden
- В файле src/garden.rs
- В файле src/garden/mod.rs
- В этом же файле между фигурных скобок, которые заменяют точку с запятой после
- Объявление подмодулей. В любом файле, кроме корня крейта, вы можете объявлять подмодули. К примеру, вы можете объявить
mod vegetables;
в src/garden.rs. Компилятор будет искать код подмодуля в каталоге с именем родительского модуля в следующих местах:- В этом же файле, сразу после
mod vegetables
и между фигурных скобок, которые заменяют точку с запятой - В файле src/garden/vegetables.rs
- В файле src/garden/vegetables/mod.rs
- В этом же файле, сразу после
- Пути к коду в модулях. После того, как модуль станет частью вашего крейта, и если допускают правила приватности, вы можете ссылаться на код в этом модуле из любого места вашего крейта, используя путь к коду. Например, тип
Asparagus
в подмодулеvegetables
модуляgarden
будет доступен по пути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!("Здесь растёт {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. Front of house — это там, где находятся клиенты; здесь размещаются места клиентов, официанты принимают заказы и оплаты, а бармены делают напитки. Back of house — там, это где повара работают на кухне, работают посудомоечные машины, а менеджеры занимаются хозяйственной деятельностью.
Чтобы структурировать крейт аналогично ресторанной модели, можно организовать размещение его функций во вложенных модулях. Создадим новую библиотеку с именем restaurant
выполнив команду cargo new restaurant --lib
. Затем вставим код из Листинга 7-1 в src/lib.rs для определения некоторых модулей и сигнатур функций. Это front of house:
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() {}
}
}
Мы определяем модуль ключевым словом mod
, затем пишем название модуля (в данном случае — front_of_house
) и размещаем фигурные скобки вокруг тела модуля. Внутри модулей можно иметь другие модули, как мы это сделали с модулями hosting
и serving
. Модули также могут содержать определения других элементов, таких как структуры, перечисления, константы, трейты или (как в Листинге 7-1) функции.
Используя модули, мы можем группировать родственные определения вместе и пояснять, в чём они являются родственными. Программистам будет легче найти необходимую функциональность в сгруппированном коде, вместо того чтобы искать её в одном общем списке. Программисты, добавляющие новые функции в этот код, будут знать, где разместить код для поддержания порядка в программе.
Earlier, we mentioned that src/main.rs and src/lib.rs are called crate roots. The reason for their name is that the contents of either of these two files form a module named crate
at the root of the crate’s module structure, known as the 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
. Дерево также показывает, что некоторые модули являются "братьями", то есть они определены в одном модуле; hosting
и serving
— это "братья", которые определены внутри front_of_house
. Если модуль A содержится внутри модуля B, мы говорим, что модуль A является потомком модуля B, а модуль B является родителем модуля A. Обратите внимание, что родителем всего дерева модулей является неявный модуль с именем crate
.
Дерево модулей может напомнить вам дерево файловой системы на компьютере; это очень удачное сравнение! По аналогии с файловой системой, модули используются для организации кода. И так же, как нам надо искать файлы в директориях, нам требуется способ поиска нужных модулей.