Организация кода с помощью модулей и крейтов

RustBeginner
Практиковаться сейчас

Введение

По мере роста программы на Rust становится сложнее понимать зоны ответственности, если все типы и функции находятся в одном файле. В лабораторной работе по CLI вы уже видели библиотеку и бинарный файл, а в лабораторной работе по тестированию — тестовый модуль. Теперь мы объединим эти подготовленные примеры в одну модель: пакет — это проект, который собирает Cargo, крейты — единицы компиляции, а модули организуют имена внутри крейта.

Вы организуете небольшой пакет для отчёта о запасах продуктов. Файлы и большая часть кода уже подготовлены, поэтому каждый шаг посвящён одной границе: предоставлению элементов из библиотечного крейта, объявлению файлового модуля и импорту небольшого публичного API в бинарный крейт.

Предоставление функции между крейтами

На этом шаге вы определите два крейта пакета и сделаете одну функцию библиотеки доступной бинарному файлу.

Перейдите в подготовленный пакет Cargo:

cd /home/labex/project/pantry-report

Файл Cargo.toml описывает пакет с именем pantry-report. Cargo распознаёт src/lib.rs как корневой файл библиотечного крейта, а src/main.rs — как корневой файл бинарного крейта. Они относятся к одному пакету, но компилируются как отдельные крейты.

Просмотрите короткие корневые файлы крейтов. Команды sed -n выводят указанные диапазоны строк, не изменяя файлы:

sed -n '1,120p' src/lib.rs
sed -n '1,120p' src/main.rs

Бинарный крейт импортирует report_title через путь библиотечного крейта. Имя пакета с дефисом в исходном коде Rust преобразуется в имя крейта с символом подчёркивания: pantry-report становится pantry_report.

Подготовленный тип возвращаемого значения &'static str обозначает заимствованный строковый литерал, действительный на протяжении всей работы программы. Маркер 'static — это аннотация времени жизни. Явные правила времени жизни выходят за рамки этого вводного курса; в этой лабораторной работе вам не нужно понимать или записывать такие аннотации.

Элементы являются закрытыми для своего модуля, если не помечены как pub. Откройте корневой файл библиотеки:

nano src/lib.rs

Измените только объявление функции:

fn report_title() -> &'static str {

на:

pub fn report_title() -> &'static str {

Ключевое слово pub включает функцию в публичный интерфейс библиотечного крейта. Сохраните файл с помощью Ctrl+O, нажмите Enter и выйдите с помощью Ctrl+X.

Используйте cargo check, чтобы проверить типы обоих крейтов без создания итоговой исполняемой сборки:

cargo check

Затем запустите бинарный крейт:

cargo run --quiet

Вывод будет таким:

Pantry Report

Это подтверждает, что бинарный крейт может пересечь границу крейта и вызвать публичную функцию библиотеки.

Объявление файлового модуля

На этом шаге вы подключите подготовленный файл inventory.rs к дереву модулей библиотечного крейта.

Исходный файл Rust не компилируется автоматически только потому, что он существует. Корневой файл крейта должен объявить модуль. Для объявления с именем inventory Rust ищет файл src/inventory.rs и помещает его элементы по пути inventory::....

Просмотрите подготовленный файл модуля. Как и раньше, команда sed -n '1,200p' использует -n, чтобы отключить автоматический вывод, а 1,200p — чтобы вывести только указанный диапазон строк:

sed -n '1,200p' src/inventory.rs

Строка #[derive(Debug)] просит Rust сгенерировать стандартную поддержку отладки для типа Item. Видимая программа не использует эту возможность, поэтому считайте её подготовленными метаданными, а не новым требованием. Тип Item, его поля и функция describe являются публичными. Вспомогательная функция availability не имеет pub, поэтому остаётся закрытой внутри модуля. describe может вызвать этот закрытый вспомогательный элемент, а внешние вызывающие объекты получают только публичный результат.

Откройте корневой файл библиотеки:

nano src/lib.rs

Замените // MODULE_DECLARATION на:

pub mod inventory;

Первое pub делает модуль доступным через библиотечный крейт. Часть mod inventory; подключает файл к дереву модулей. Сохраните файл и выйдите из Nano, затем проверьте пакет:

cargo check
Finished `dev` profile ...

Успешная проверка подтверждает, что Rust нашёл src/inventory.rs и скомпилировал его как pantry_report::inventory.

Импорт и использование публичного API

На этом шаге вы воспользуетесь объявлением use, чтобы добавить элементы модуля в область видимости бинарного крейта и вывести запись о запасах продуктов.

Без импорта полные пути выглядят так: pantry_report::inventory::Item и pantry_report::inventory::describe. Объявление use создаёт в текущей области видимости короткие имена, не перемещая и не копируя сами элементы.

Откройте исходный файл бинарного крейта:

nano src/main.rs

Замените // INVENTORY_IMPORT на этот сгруппированный импорт:

use pantry_report::inventory::{describe, Item};

Фигурные скобки объединяют два элемента с одинаковым префиксом пути. Затем замените // INVENTORY_REPORT на:

    let lentils = Item {
        name: String::from("lentils"),
        quantity: 4,
    };
    println!("{}", describe(&lentils));

Бинарный крейт может создать Item, потому что тип и оба его поля являются публичными. Он передаёт элемент в describe по ссылке; эта публичная функция внутри своего модуля вызывает закрытую функцию availability.

Сохраните файл и выйдите из Nano, затем запустите пакет:

cargo run --quiet
Pantry Report
lentils: 4 jars (stocked)

Эти две строки показывают, что обмен элементами между крейтами и организация кода по модулям работают через небольшой публичный API.

Итоги

Вы работали с одним пакетом Cargo, содержащим библиотечный и бинарный крейты, сделали элемент библиотеки публичным, объявили файловый модуль, использовали пути крейтов и модулей, сократили эти пути с помощью use и оставили вспомогательную функцию реализации закрытой за небольшим публичным API.