ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1133
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Rust имеет ряд функций, которые позволяют управлять организацией кода, в том числе управлять тем какие детали открыты, какие детали являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые модульной
системой включают в себя:
Пакеты: Функционал Cargo позволяющий собирать, тестировать и делиться крейтами
Крейты: Дерево модулей, которое создаёт библиотечный или исполняемый файл
Модули и use: Позволяют вместе контролировать организацию, область видимости и скрытие путей
Пути: способ именования элемента, такого как структура, функция или модуль
В этой главе мы рассмотрим все эти функции, обсудим как они взаимодействуют и объясним, как использовать их для управления областью видимости. К концу у вас должно появиться солидное понимание модульной системы и умение работать с областями видимости на уровне профессионала!
Пакеты и крейты
Первые части модульной системы, которые мы рассмотрим — это пакеты и крейты.
Крейт — это наименьший объем кода, который компилятор Rust рассматривает за раз.
Даже если вы запустите rustc вместо cargo и передадите один файл с исходным кодом
(как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1),
компилятор считает этот файл крейтом. Крейты могут содержать модули, и модули могут быть определены в других файлах, которые компилируются вместе с крейтом, как мы увидим в следующих разделах.
Крейт может быть одним из двух видов: бинарный крейт или библиотечный крейт.
Бинарные крейты — это программы, которые вы можете скомпилировать в исполняемые файлы, которые вы можете запускать, например программу командной строки или сервер. У каждого бинарного крейта должна быть функция с именем main
, которая определяет, что происходит при запуске исполняемого файла. Все крейты, которые мы создали до сих пор, были бинарными крейтами.
Библиотечные крейты не имеют функции main и не компилируются в исполняемый файл. Вместо этого они определяют функциональность, предназначенную для совместного использования другими проектами. Например, крейт rand
, который мы использовали в
Главе 2
обеспечивает функциональность, которая генерирует случайные числа. В большинстве случаев, когда Rustaceans говорят «крейт», они имеют в виду библиотечный крейт, и они используют «крейт» взаимозаменяемо с общей концепцией программирования «библиотека».
Корневой модуль крейта — это исходный файл, из которого компилятор Rust начинает собирать корневой модуль вашего крейта (мы подробно объясним модули в разделе
«Определение модулей для контроля видимости и закрытости»
).
Пакет — это набор из одного или нескольких крейтов, предоставляющий набор функциональности. Пакет содержит файл Cargo.toml, в котором описывается, как собирать эти крейты. На самом деле Cargo — это пакет, содержащий бинарный крейт для инструмента командной строки, который вы использовали для создания своего кода.
Пакет Cargo также содержит библиотечный крейт, от которого зависит бинарный крейт.
Другие проекты тоже могут зависеть от библиотечного крейта Cargo, чтобы использовать ту же логику, что и инструмент командной строки Cargo.
Пакет может содержать сколько угодно бинарных крейтов, но не более одного библиотечного крейта. Пакет должен содержать хотя бы один крейт, библиотечный или бинарный.
Давайте пройдёмся по тому, что происходит, когда мы создаём пакет. Сначала введём команду cargo new
:
После того, как мы запустили cargo new
, мы используем ls
, чтобы увидеть, что создал
Cargo. В каталоге проекта есть файл Cargo.toml, дающий нам пакет. Также есть каталог src,
содержащий main.rs. Откройте Cargo.toml в текстовом редакторе и обратите внимание,
что в нём нет упоминаний о src/main.rs. Cargo следует соглашению о том, что src/main.rs
— это корневой модуль бинарного крейта с тем же именем, что и у пакета. Точно так же
Cargo знает, что если каталог пакета содержит src/lib.rs, пакет содержит библиотечный крейт с тем же именем, что и пакет, а src/lib.rs является корневым модулем этого крейта.
Cargo передаёт файлы корневого модуля крейта в rustc для сборки библиотечного или бинарного крейта.
Здесь у нас есть пакет, который содержит только src/main.rs, что означает, что он содержит только бинарный крейт с именем my-project
. Если пакет содержит src/main.rs и
src/lib.rs, он имеет два крейта: бинарный и библиотечный, оба с тем же именем, что и пакет. Пакет может иметь несколько бинарных крейтов, помещая их файлы в каталог
src/bin: каждый файл будет отдельным бинарным крейтом.
$
cargo new my-project
Created binary (application) `my-project` package
$
ls my-project
Cargo.toml src
$
ls my-project/src main.rs
Определение модулей для контроля видимости и
закрытости
В этом разделе мы поговорим о модулях и других частях системы модулей, а именно:
путях (paths), которые позволяют именовать элементы; ключевом слове use
, которое приносит путь в область видимости; ключевом слове pub
, которое делает элементы общедоступными. Мы также обсудим ключевое слово as
, внешние пакеты и оператор glob. А пока давайте сосредоточимся на модулях!
Во-первых, мы начнём со списка правил, чтобы вам было легче ориентироваться при организации кода в будущем. Затем мы подробно объясним каждое из правил.
Шпаргалка по модулям
Здесь мы даём краткий обзор того, как модули, пути, ключевое слово 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
, содержит следующие файлы и директории:
Файл корневого модуля крейта в нашем случае src/main.rs, и его содержимое:
Файл: src/main.rs
Строка pub mod garden;
говорит компилятору о подключении кода, найденном в
src/garden.rs:
Файл: src/garden.rs
А здесь pub mod vegetables;
указывает на подключаемый код в src/garden/vegetables.rs.
Этот код:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs use crate::garden::vegetables::Asparagus; pub mod garden; fn main
() { let plant = Asparagus {}; println!
(
"I'm growing {:?}!"
, plant);
} pub mod vegetables;
#[derive(Debug)]
pub struct
Asparagus
{}
Теперь давайте рассмотрим детали этих правил и продемонстрируем их в действии!
Группировка связанного кода в модулях
Модули позволяют упорядочивать код внутри крейта для удобочитаемости и лёгкого повторного использования. Модули также позволяют нам управлять приватностью
элементов, поскольку код внутри модуля по умолчанию является закрытым. Частные элементы — это внутренние детали реализации, недоступные для внешнего использования. Мы можем сделать модули и элементы внутри них общедоступными, что позволит внешнему коду использовать их и зависеть от них.
В качестве примера, давайте напишем библиотечный крейт предоставляющий функциональность ресторана. Мы определим сигнатуры функций, но оставим их тела пустыми, чтобы сосредоточиться на организации кода, вместо реализации кода для ресторана.
В ресторанной индустрии некоторые части ресторана называются фронтом дома, а другие задней частью дома. Фронт дома это там где находятся клиенты; здесь размещаются места клиентов, официанты принимают заказы и оплаты, а бармены делают напитки. Задняя часть дома это где шеф-повара и повара работают на кухне,
работают посудомоечные машины, а менеджеры занимаются административной деятельностью.
Чтобы структурировать крейт аналогично тому, как работает настоящий ресторан,
можно организовать размещение функций во вложенных модулях. Создадим новую библиотеку (библиотечный крейт) с именем restaurant выполнив команду cargo new restaurant --lib
; затем вставим код из листинга 7-11 в src/lib.rs для определения некоторых модулей и сигнатур функций. Это секция фронта дома:
Файл: 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 в корне структуры модулей крейта, известной как
дерево модулей.
В листинге 7-2 показано дерево модулей для структуры модулей, приведённой в коде листинга 7-1.
Листинг 7-2: Дерево модулей для для структуры модулей приведённой в коде в листинге 7-1
Это дерево показывает, как некоторые из модулей вкладываются друг в друга; например, hosting находится внутри front_of_house
. Дерево также показывает, что некоторые модули являются братьями (siblings) друг для друга, то есть они определены в одном модуле; hosting и serving это братья которые определены внутри front_of_house
Если модуль A содержится внутри модуля B, мы говорим, что модуль A является
*потомком * (child) модуля B, а модуль B является *родителем * (parent) модуля A.
Обратите внимание, что родителем всего дерева модулей является неявный модуль с именем crate
Дерево модулей может напомнить вам дерево каталогов файловой системы на компьютере; это очень удачное сравнение! По аналогии с каталогами в файловой системе, мы используется модули для организации кода. И так же, как нам надо искать файлы в каталогах на компьютере, нам требуется способ поиска нужных модулей.
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Пути для ссылки на элемент в дереве модулей
Чтобы показать Rust, где найти элемент в дереве модулей, мы используем путь так же,
как мы используем путь при навигации по файловой системе. Чтобы вызвать функцию,
нам нужно знать её путь.
Пути бывают двух видов:
абсолютный путь - это полный путь, начинающийся от корневого модуля крейта;
для кода из внешнего крейта абсолютный путь начинается с имени крейта, а для кода из текущего крейта он начинается с литерала crate
относительный путь начинается с текущего модуля и использует ключевые слова self
, super или идентификатор в текущем модуле.
Как абсолютные, так и относительные, пути состоят из одного или нескольких идентификаторов, разделённых двойными двоеточиями (
::
).
Вернёмся к листингу 7-1, скажем, мы хотим вызвать функцию add_to_waitlist
. Это то же самое, что спросить: какой путь у функции add_to_waitlist
? В листинге 7-3 мы немного упростили код листинга 7-1, удалив некоторые модули и функции.
Мы покажем два способа вызова функции add_to_waitlist из новой функции eat_at_restaurant
, определённой в корневом модуле крейта. Эти пути правильные, но остаётся ещё одна проблема, которая не позволит этому примеру скомпилироваться как есть. Мы скоро объясним почему.
Функция eat_at_restaurant является частью общедоступного API нашего библиотечного крейта, поэтому мы помечаем её ключевым словом pub
. В разделе "Раскрываем приватные пути с помощью ключевого слова pub
"
мы рассмотрим более подробно pub
Файл: src/lib.rs
Листинг 7-3. Вызов функции
add_to_waitlist
с использованием абсолютного и относительного пути
mod front_of_house { mod hosting { fn add_to_waitlist
() {}
}
} pub fn eat_at_restaurant
() {
// Absolute path crate::front_of_house::hosting::add_to_waitlist();
// Relative path front_of_house::hosting::add_to_waitlist();
}
При первом вызове функции add_to_waitlist из eat_at_restaurant мы используем абсолютный путь. Функция add_to_waitlist определена в том же крейте, что и eat_at_restaurant
, и это означает, что мы можем использовать ключевое слово crate в
начале абсолютного пути. Затем мы добавляем каждый из последующих дочерних модулей, пока не составим путь до add_to_waitlist
. Вы можете представить себе файловую систему с такой же структурой: мы указываем путь
/front_of_house/hosting/add_to_waitlist для запуска программы add_to_waitlist
;
использование имени crate в качестве корневого модуля крейта аналогично использованию
/
для указания корня файловой системы в вашей оболочке.
Второй раз, когда мы вызываем add_to_waitlist из eat_at_restaurant
, мы используем относительный путь. Путь начинается с имени модуля front_of_house
, определённого на том же уровне дерева модулей, что и eat_at_restaurant
. Для эквивалентной файловой системы использовался бы путь front_of_house/hosting/add_to_waitlist
Начало пути с имени модуля означает, что путь является относительным.
Выбор, использовать относительный или абсолютный путь, является решением, которое вы примете на основании вашего проекта. Решение должно зависеть от того, с какой вероятностью вы переместите объявление элемента отдельно от или вместе с кодом использующим этот элемент. Например, в случае перемещения модуля front_of_house и его функции eat_at_restaurant в другой модуль с именем customer_experience
, будет необходимо обновить абсолютный путь до add_to_waitlist
, но относительный путь всё
равно будет действителен. Однако, если мы переместим отдельно функцию eat_at_restaurant в модуль с именем dining
, то абсолютный путь вызова add_to_waitlist останется прежним, а относительный путь нужно будет обновить. Мы предпочитаем указывать абсолютные пути, потому что это позволяет проще перемещать определения кода и вызовы элементов независимо друг от друга.
Давайте попробуем скомпилировать код из листинга 7-3 и выяснить, почему он ещё не компилируется. Ошибка, которую мы получаем, показана в листинге 7-4.