Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Модули: область видимости и приватность

В этом разделе мы поговорим о модулях и других частях системы модулей: о путях, которые позволяют называть элементы; о ключевом слове 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, и в нём вот что:

Файл: 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, а там вот что:

Файл: 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 — он определяет несколько модулей и сигнатур функций. Это «зал».

Файл: src/lib.rs
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() {}
    }
}
Листинг 7-1: Модуль 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
Листинг 7-2: Дерево модулей для кода из листинга 7-1

Дерево показывает, какие модули вложены в другие: например, hosting вложен в front_of_house. Видно и то, что некоторые модули — соседи (siblings), то есть определены в одном и том же модуле: hosting и serving — соседи внутри front_of_house. Если модуль A находится внутри модуля B, говорят, что модуль A — потомок (child) модуля B, а B — родитель (parent) модуля A. Обратите внимание: корнем всего дерева модулей служит неявный модуль с именем crate.

Дерево модулей может напомнить вам дерево папок в файловой системе — и это очень удачное сравнение! Как папки в файловой системе, модули служат для организации кода. И точно так же, как файлы в папках, модули нужно уметь находить.