Программирование PC Rust: управление проектами с помощью пакетов, крейтов и модулей Sun, September 08 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Rust: управление проектами с помощью пакетов, крейтов и модулей Печать
Добавил(а) microsin   

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

Программы, которые мы писали до сих пор, состояли из единственного модуля в одном файле. В процессе разрастания проекта вы должны упорядочить его код путем разделения его на модули, и затем на несколько файлов. Пакет может состоять из нескольких бинарных крейтов (crates) и опционально одного библиотечного крейта (library crate). По мере роста пакета вы можете извлечь его части в отдельные крейты, которые становятся внешними зависимостями (external dependencies). Эта глава (перевод документации [1]) раскрывает соответствующие техники на языке Rust. Для очень крупных проектов, состоящих из набора взаимосвязанных пакетов, которые развиваются вместе, Cargo предоставляет рабочие пространства (workspaces). Рабочие пространства рассматриваются в секции "Cargo Workspaces" главы 14.

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

С этим связано понятие области действия (или области видимости, scope): вложенный контекст, в котором написан код, имеет набор имен, который определен как "in scope". Когда читается, пишется и компилируется код, программисты и компиляторы должны знать, относится или конкретное имя в конкретном месте к переменной, функции, структуре, перечислению, модулю, константе или другому элементу, и что этот элемент означает. Вы можете создавать области действия и изменять, какие имена входят в их область действия, или какие нет. Вы не можете создать два элемента с одинаковыми именами в одной и той же области действия; доступны инструменты для разрешения конфликтов имен.

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

Пакеты (packages): фича Cargo, которая позволяет делать сборку, тестирование, и предоставление в общий доступ крейтов (crates).
Крейты (crates): дерево модулей, составляющее библиотеку или исполняемый бинарник.
Модули и их использование: позволяет вам управлять организацией, областью действия и приватностью путей.
Пути (paths): способ именования элементов, таких как структура (struct), функция или модуль.

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

[Пакеты и крейты]

Первое, что мы рассмотрим из системы модулей, это пакеты (packages) и крейты (crates).

Крейт это наименьшее количество кода, которое компилятор Rust рассматривает одновременно. Даже если вы запускаете rustc вместо cargo, и передаете один исходный файл кода (как мы делали в секции "Написание и запуск программы Rust" главы 1, см. [2]), компилятор все равно считает файл крейтом. Крейты могут содержать модули, и модули могут быть определены в других файлах, которые компилируются вместе с крейтами, как мы увидим в последующих секциях этой статьи.

Крейт может быть в двух формах: двоичный крейт (binary crate) или библиотечный крейт (library crate). Двоичные крейты это программы, которые вы можете компилировать в исполняемый файл, который можно запустить, такие как программа командной строки или сервер. Каждый из них должен иметь функцию с именем main, которая определяет, что происходит при запуске исполняемого файла. Все крейты, которые мы создавали до сих пор, были бинарными.

У библиотечного крейта нет функции main, и они не компилируются в исполняемый файл. Вместо этого они определяют функционал, предназначенный для совместного использования в нескольких проектах. Например, крейт rand, который мы использовали в главе 2 (см. [3]), предоставляет функционал генерации случайных чисел. В большинстве случаев, когда говорят про крейт, имеют в виду использование этого термина в том же контексте, что и термин библиотека.

Крейт root это исходный файл, с которого компилятор Rust начинает составление корневого модуля вашего крейта (мы подробно рассмотрим модули в секции "Определение модулей для управления областью действия и приватностью").

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

Пакет может содержать столько двоичных крейтов, сколько вы захотитеA, однако большинство состоят только из одного библиотечного крейта. Пакет должен содержать как минимум один крейт, будь то библиотечный крейт или двоичный.

Давайте посмотрим что происходит, когда мы создаем пакет. Сначала мы вводим команду cargo new:

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

После запуска cargo new мы использовали команду ls, чтобы посмотреть, что создал Cargo. В директории проекта мы видим файл Cargo.toml, который дает нам пакет. Также в директории src появился файл main.rs. Откройте файл Cargo.toml в текстовом редакторе, и обратите внимание на то, что в нем нет упоминания об src/main.rs. Cargo следует соглашению, что src/main.rs это root бинарного крейта с тем же именем, что и пакет. Аналогично Cargo знает, что если директория пакета содержит src/lib.rs, то пакет содержит библиотечный крейт с тем же именем, что и пакет, и src/lib.rs это root библиотечного крейта. Cargo передает файлы crate root в компилятор rustc, чтобы собрать библиотеку или бинарник.

В этом примере у нас пакет, который только лишь содержит src/main.rs. Это означает, что пакет содержит только двоичный крейт с именем my-project. Если пакет содержит src/main.rs и src/lib.rs, то у него есть 2 крейта: бинарный и библиотечный, с одинаковыми именами, совпадающими с именем пакета. В пакете может быть несколько двоичных крейтов, размещенных в директории src/bin: каждый файл будет отдельным двоичным крейтом.

[Определение модулей для управления областью действия и приватностью]

В этой секции мы поговорим про модули и другие части системы модулей, а именно про пути (paths), которые позволяют вам именовать элементы кода. Ключевое слово use приводит путь в область действия, и ключевое слово pub делает элементы публичными. Также мы обсудим ключевое слово as, внешние пакеты и оператор glob.

Для удобства мы начнем со списка правил, что поможет вам в будущем при организации своего кода. Затем подробно объясним каждое правило.

Шпаргалка по модулям. Здесь мы даем краткий справочник о том, как модули, пути, ключевые слова use и pub работают в компиляторе, и как большинство разработчиков организуют свой код. В этой главе мы рассмотрим каждое из этих правил, но этот справочник послужит хорошим напоминанием от том, как работают эти модули.

Начало в crate root: когда компилируется крейт, компилятор сначала ищет файла корня крейта (crate root, обычно это src/lib.rs для библиотечного крейта или src/main.rs для бинарного крейта) для компиляции кода.

Декларирование модулей: в файле crate root вы можете декларировать новые модули; скажем, вы декларируете модуль "garden" с помощью mod garden;. Компилятор ищет код модуля в следующих местах:

- Inline, внутри фигурных скобок, которые заменяют точку с запятой, идущую за mod garden.
- В файле src/garden.rs.
- В файле src/garden/mod.rs.

Декларирование субмодулей: в любом файле, отличном от crate root, вы можете декларировать субмодули. Например, вы можете декларировать mod vegetables; в файле src/garden.rs. Компилятор будет искать код субмодуля в директории, именованной для родительского модуля в следующих местах:

- Inline, напрямую после mod vegetables, в пределах фигурных скобок вместо точки с запятой.
- В файле src/garden/vegetables.rs.
- В файле src/garden/vegetables/mod.rs.

Пути к коду в модулях: как только модуль станет частью вашего крейта, вы можете ссылаться на код в этом модуле из любого места в том же крейте, пока это позволяют правила приватности, используя путь до кода. Например, тип Asparagus в модуле garden vegetables будет найден по имени crate::garden::vegetables::Asparagus.

Private или public: по умолчанию код в пределах модуля приватен из родительских модулей. Чтобы сделать модуль публичным, декларируйте его с ключевыми словами pub mod вместо mod. Чтобы сделать также элементы публичными в публичном модуле, используйте pub перед их декларацией.

Ключевое слово use: внутри области действия ключевое слово use создает сокращения (shortcut, шорткат) для имен элементов, чтобы уменьшить количество повторений длинных путей. В любой области действия, которая может ссылаться на crate::garden::vegetables::Asparagus, вы можете создать шорткат с использованием crate::garden::vegetables::Asparagus; и затем вам нужно только написать Asparagus, чтобы использовать этот тип в этой области действия.

Здесь мы создали двоичный крейт с именем backyard, который иллюстрирует эти правила. Директория крейта также носит имя backyard, и она содержит следующие файлы и директории:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

Файл crate root в этом случае будет 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 {}

Теперь давайте рассмотрим в подробностях эти правила в действии.

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

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

В ресторанной индустрии некоторые части ресторана относятся к переднему виду дома (front), а другие к задней части дома (back). Front это то место, где находятся пользователи ресторана. Это включает в себя то, как хозяева рассаживают посетителей; обслуживают заказы и их оплату, а бармены делают напитки. Back это то место, где повара готовят еду на кухне, убирают посудомоечные машины, а менеджеры выполняют административную работу.

Чтобы структурировать наш крейт по такому принципу, мы можем организовать его в виде вложенных модулей. Создайте новую библиотеку с именем restaurant путем запуска команды cargo new restaurant --lib; затем введите код из листинга 7-1 в 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 root). Причина такого названия заключается в том, что содержимое любого из этих двух файлов формируют модуль под названием крейт (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. Дерево также показывает, что некоторые модули являются родственными по отношению друг к другу, т. е. они определены в одном и том же модуле; hosting и serving здесь родственники, определенные внутри front_of_house. Если модуль A содержится внутри модуля B, то мы говорим, что модуль A является дочерним по отношению к модулю B, и модуль B родительский по отношению к модулю A. Обратите внимание, что все дерево модулей имеет корень под неявным модулем, который называется crate.

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

[Пути для ссылок на элементы в дереве модулей]

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

Путь может быть в двух формах:

Абсолютный путь, начинающийся от crate root; для кода из внешнего крейта абсолютный путь начинается с имени крейта, и для кода из текущего крейта абсолютный путь начинается с литерального слова crate.
Относительный путь, начинающийся из текущего модуля и использующий self, super или идентификатор в текущем модуле.

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

Возвращаясь к листингу 7-1: скажем, нам нужно вызвать функцию add_to_waitlist. Это то же самое, что сказать: какой путь до функции add_to_waitlist? Листинг 7-3 содержит листинг 7-1 с некоторыми удаленными модулями и функциями.

Мы покажем 2 способа вызова функции add_to_waitlist из новой функции eat_at_restaurant, определенной в crate root. Эти пути корректны, однако остается другая проблема, которая не даст этому примеру скомпилироваться как есть. Мы немного объясним, почему так.

Функция eat_at_restaurant является частью публичного API нашего библиотечного крейта, поэтому мы помечаем её ключевым словом pub. В секции "Предоставление путей с ключевым словом pub" мы более подробно поговорим про pub.

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() { // Абсолютный путь: crate::front_of_house::hosting::add_to_waitlist();
// Относительный путь: front_of_house::hosting::add_to_waitlist(); }

Листинг 7-3. Вызов функции 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 для старта из crate root подобно / для старта из корня файловой системы вашего шелла.

Второй раз, когда мы вызвали 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.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^  --------------- function
  |                `add_to_waitlist` is not publicly re-exported
  |                            |
  |                            private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private --> src/lib.rs:12:21 | 12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ --------------- function | `add_to_waitlist` is not publicly re-exported | | | private module | note: the module `hosting` is defined here --> src/lib.rs:2:5 | 2 | mod hosting { | ^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` (lib) due to 2 previous errors

Листинг 7-4. Ошибки компилятора при сборке кода в листинге 7-3.

Сообщения об ошибках говорят, что модуль hosting приватный. Другими словами, у нас заданы корректные пути для модуля hosting и функции add_to_waitlist, однако Rust не позволяет использовать их, потому что нет доступа к приватным секциям. В Rust все элементы кода (функции, методы, структуры, перечисления, модули и константы) по умолчанию являются приватными для родительских модулей. Если вы хотите сделать элемент наподобие функции или структуры приватным, то вы помещаете его в модуль.

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

Разработчики Rust выбрали функционал системы модулей таким образом, чтобы скрытие внутренних деталей реализации было по умолчанию. Таким образом вы знаете, какие части внутреннего кода вы можете изменить, не нарушая этим внешний код. Тем не менее Rust дает вам возможность раскрыть внутренние части кода дочерних модулей для внешних модулей-предков, используя ключевое слово pub, чтобы сделать элемент кода публичным.

Раскрытие путей с помощью ключевого слова pub. Давайте вернемся к ошибке в листинге 7-4, которая говорит, что модуль hosting приватный. Мы хотим, чтобы функция eat_at_restaurant в родительском модуле получила доступ к функции add_to_waitlist дочернего модуля, для этого мы помечаем модуль hosting ключевым словом pub, как показано в листинге 7-5.

mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() { // Абсолютный путь: crate::front_of_house::hosting::add_to_waitlist();
// Относительный путь: front_of_house::hosting::add_to_waitlist(); }

Листинг 7-5. Декларирование модуля hosting как публичного (pub), чтобы его можно было использовать из eat_at_restaurant.

К сожалению, компиляция кода из листинга 7-5 все еще дает ошибку, как показано в листинге 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `add_to_waitlist` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn add_to_waitlist() {}
  |         ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private --> src/lib.rs:12:30 | 12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ private function | note: the function `add_to_waitlist` is defined here --> src/lib.rs:3:9 | 3 | fn add_to_waitlist() {} | ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` (lib) due to 2 previous errors

Листинг 7-6. Ошибки компилятора при попытке собрать код листинга 7-5.

Что происходит? Добавление ключевого слова pub перед mod hosting сделало этот модуль публичным. С этим изменением, если мы можем обращаться к front_of_house, мы можем обращаться к hosting. Однако содержимое hosting все еще приватное; превращение модуля в публичным все еще не делает автоматически его содержимое также публичным. Ключевое слово pub на модуле только позволяет его родительским модулям ссылаться на него, но не обращаться к его внутреннему коду. Поскольку модули это контейнеры, мы мало что можем сделать, только лишь превратив модуль в публичный; нам нужно пойти дальше и сделать один или большее количество его элементов также публичными.

Ошибки листинга 7-6 говорят, что функция add_to_waitlist приватная. Правила приватности применяются к структурам, перечислениям и методам так же, как и к модулям.

Давайте также сделаем публичной функцию add_to_waitlist путем добавления ключевого слова pub перед её определением, как в листинге 7-7.

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() { // Абсолютный путь: crate::front_of_house::hosting::add_to_waitlist();
// Относительный путь: front_of_house::hosting::add_to_waitlist(); }

Листинг 7-7. Добавление ключевого слова pub к mod hosting и fn add_to_waitlist позволит нам вызвать эту функцию из eat_at_restaurant.

Теперь код компилируется! Чтобы понять, почему добавление ключевого слова pub позволило нам использовать эти пути в eat_at_restaurant с правилами приватности, давайте посмотрим на абсолютный и относительный пути.

В абсолютном пути мы начали его с crate, корня нашего дерева модулей крейта. Модуль front_of_house определен в crate root. Хотя модуль front_of_house не публичный, потому что функция eat_at_restaurant определена в том же модуле, что и функция front_of_house (т. е. функции eat_at_restaurant и front_of_house близкие родственники), мы можем ссылаться на front_of_house из eat_at_restaurant (они в одной области видимости). Следующий модуль hosting помечен как pub. Мы можем обращаться к родительскому модулю hosting. И наконец, функция add_to_waitlist помечена как pub, и мы можем обращаться к ней из её родительского модуля, так что её вызов работает.

С относительными путями логика какая же, как и для абсолютных путей, кроме первого шага: вместо того, чтобы начинать путь от crate root, путь начинается от front_of_house. Модуль front_of_house определен внутри того же самого модуля, что и eat_at_restaurant, так что относительный путь, начинающийся от модуля в котором определен eat_at_restaurant, работает. Тогда, поскольку hosting и add_to_waitlist помечены pub, остальной путь работает, и этот вызов функции допустимый.

Если вы планируете предоставлять в общий доступ ваш библиотечный крейт, чтобы другие проекты могли использовать его код, ваш публичный API это ваше соглашение с пользователями вашего крейта, и этот публичный API определяет, как пользователи могут взаимодействовать с вашим кодом. Существует много соображений по поводу управления изменениями в вашем публичном API, чтобы упростить его для зависимостей. Эти соображения выходят за рамки этой книги; если вы интересуетесь такой темой, см. рекомендации по реализации Rust API [4].

Мы упоминали, что пакет может содержать оба корневых крейта: как двоичный crate root в файле src/main.rs, так и библиотечный crate root в файле src/lib.rs, и оба этих крейта получают по умолчанию имя пакета. Обычно пакеты с таким шаблоном, содержащие оба крейта, библиотечный и бинарный, будут иметь только необходимый код в бинарном крейте, чтобы запустить исполняемый код, который вызывает код из библиотечного крейта. Это позволит другим проектам получить выгоду от большинства функционала, предоставляемого пакетом, потому что что код библиотечного крейта может быть предоставлен в общий доступ.

Дерево модулей должно быть определено в src/lib.rs. Затем любые публичные элементы кода могут быть использованы в двоичном крейте путем указании начала пути с имени пакета. Бинарный крейт становится пользователем библиотечного крейта точно так же, как полностью внешний крейт будет использовать библиотечный крейт: он может использовать только публичный API. Это поможет вам разрабатывать качественный API; вы будете не только автором кода, но также и его клиентом!

В главе 12 будут демонстрироваться организационные практики с программой командной строки, где будут находиться оба крейта, бинарный и библиотечный.

Начало относительных путей через super. Мы можем составлять относительные пути, которые начинаются в родительском модуле, вместо того чтобы начинать путь от текущего модуля или crate root, путем вставки super в начало пути. Это похоже на начало пути файловой системы с синтаксисом предыдущего каталога (синтаксис ..). Использование super позволит нам ссылаться на элемент, про который мы знаем, что он находится в родительском модуле, что может облегчить перестановку дерева модулей, когда модуль тесно связан с родительским, но родительский может быть перемещен когда-нибудь в другое место дерева модулей.

Рассмотрим код в листинге 7-8, который моделирует ситуацию, в которой шеф-повар исправил неправильный заказ и персонально принес его заказчику. Функция fix_incorrect_order определенная в модуле back_of_house, вызывает функцию deliver_order, определенную в родительском модуле, путем указания пути к deliver_order, начинающегося через super:

fn deliver_order() {}

mod back_of_house { fn fix_incorrect_order() { cook_order(); super::deliver_order(); }
fn cook_order() {} }

Листинг 7-8. Вызов функции с использованием super в относительном пути.

Функция fix_incorrect_order находится в модуле back_of_house, так что мы можем использовать super, чтобы перейти к родительскому модулю модуля back_of_house, который в этом случае корневой крейт (crate root). Отсюда мы ищем и успешно находим deliver_order. Мы думаем, что вероятно модуль back_of_house и функция deliver_order останутся в одних и тех же отношениях друг с другом, и будут перемещены вместе, если мы решим реорганизовать дерево модулей. Таким образом, мы используем super, поэтому у нас будет меньше мест для обновления кода в будущем, если этот код будет перемещен в другой модуль.

Предоставление публичного доступа к структурам и перечислениям. Мы можем также использовать pub для обозначения публичными структур и перечислений, однако здесь есть несколько дополнительных подробностей по использованию pub вместе со структурами и перечислениями. Если мы используем pub перед определением структуры, то делаем её публичной, однако поля структуры все еще остаются приватными. Мы можем сделать каждое поле публичным, или не публичным, делая выбор для каждого конкретного случая. В листинге 7-9 мы определили публичной структуру с публичным полем toast, но с приватным полем seasonal_fruit. Это моделирует случай в ресторане, где клиент может выбрать тип хлеба, который подается с заказом, однако шеф-повар решает, какие фрукты сопровождают еду, исходя из того, что в текущем сезоне находится на складе. Доступные фрукты быстро меняются, поэтому клиенты могут выбрать фрукты или даже посмотреть, какие фрукты они получат.

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }
impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), } } } }

pub fn eat_at_restaurant() { // Заказ завтрака летом с ржаным тостом (Rye toast): let mut meal = back_of_house::Breakfast::summer("Rye"); // Измените наше мнение о том, какой хлеб мы хотели бы: meal.toast = String::from("Wheat"); println!("Я хотел бы {} тост, пожалуйста", meal.toast);
// Следующая строка не скомпилируется, если мы её раскомментируем; // нам не разрешено увидеть или модифицировать сезонные фрукты, // которые подадут с едой. // meal.seasonal_fruit = String::from("blueberries"); }

Листинг 7-9. Структура с некоторыми публичными полями и некоторыми приватными полями.

Поскольку поле toast публичное в структуре back_of_house::Breakfast, в eat_at_restaurant мы можем записать и прочитать поле toast, используя нотацию точки. Обратите внимание, что мы не можем использовать поле seasonal_fruit в eat_at_restaurant, потому что seasonal_fruit приватное поле. Попробуйте раскомментировать строку, где модифицируется значение поля seasonal_fruit, чтобы увидеть соответствующую ошибку!

Также обратите внимание, что потому что в back_of_house::Breakfast есть приватное поле, структура нуждается в предоставлении связанной публичной функции, которая конструирует экземпляр Breakfast (здесь мы её назвали summer). Если в Breakfast нет такой функции, то мы не сможем создать экземпляр Breakfast в eat_at_restaurant, потому что мы не можем установить значение поля seasonal_fruit в eat_at_restaurant.

Напротив, если мы сделаем перечисление публичным, то все её варианты станут публичными. Нам нужно только вставить pub перед ключевым словом enum, как показано в листинге 7-10.

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad; }

Листинг 7-10. Обозначение enum публичным, чтобы все его варианты стали публичными.

Поскольку мы сделали перечисление Appetizer публичным, мы можем использовать варианты Soup и Salad в eat_at_restaurant.

Перечисления не очень полезны, пока её варианты не публичны; было бы раздражающим иметь аннотацию для каждого варианта перечисления с pub, так что по умолчанию варианты перечисления публичны. Структуры часто полезны без того, чтобы их поля были публичными, так что поля структуры следуют общему правилу, что все поля по умолчанию приватные, если не было поле структуры специально опубликовано через pub.

Есть еще одна ситуация, связанная с pub, которую мы не рассмотрели, и это наша последняя фича системы модулей: ключевое слово use. Сначала мы рассмотрим применение use самостоятельно, и затем покажем, как комбинировать pub и use.

[use: приведение путей в область видимости]

Необходимость выписывать пути к функциям может показаться неудобной и повторяющийся. В листинге 7-7, независимо от того, выбрали ли мы абсолютный или относительный путь к функции add_to_waitlist, каждый раз, когда мы хотим вызвать add_to_waitlist, нам приходилось указывать front_of_house и также hosting. К счастью, есть способ упростить этот процесс: мы можем создать сокращение для пути с помощью однократного использования ключевого слова use, и затем можно использовать короткое имя везде.

В листинге 7-11, мы привели модуль crate::front_of_house::hosting в область видимости функции eat_at_restaurant function, так что мы только указываем hosting::add_to_waitlist для вызова функции add_to_waitlist в eat_at_restaurant.

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }

Листинг 7-11. Приведение модуля в область действия с помощью use.

Добавление use и пути области действия подобно созданию символической ссылки в файловой системе. Добавлением use crate::front_of_house::hosting в crate root имя модуля hosting теперь становится допустимым в этой области действия, как если бы модуль hosting был определен в crate root. Пути, приведенные в область действия через use, также проверяют приватность, как и любые другие пути.

Обратите внимание, что use только создает шорткат для частной области действия, в которой встретилось use. Листинг 7-12 перемещает функцию eat_at_restaurant в новый дочерний модуль с именем customer, который затем в другой области, чем оператор use, так что тело функции не скомпилируется:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer { pub fn eat_at_restaurant() { hosting::add_to_waitlist(); } }

Листинг 7-12. Оператор use применяется только в той области, где он есть.

Ошибка компилятора показывает, что шорткат больше не применяется с модулем customer:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of undeclared crate or module `hosting`
   |
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |
warning: unused import: `crate::front_of_house::hosting` --> src/lib.rs:7:5 | 7 | use crate::front_of_house::hosting; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`. warning: `restaurant` (lib) generated 1 warning error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

Обратите также на то, что здесь присутствует предупреждение, что use больше не используется в его области! Чтобы исправить эту проблему, переместите use также в модуль customer, или сделайте шорткат в родительском модуле через super::hosting within дочернего модуля customer.

Создание идиоматических путей use. В листинге 7-11 вы возможно были удивлены, почему мы указали use crate::front_of_house::hosting и затем вызвали hosting::add_to_waitlist в eat_at_restaurant вместо того, чтобы указать путь use на всем пути к функции add_to_waitlist для достижении того же результата, что и в листинге 7-13.

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() { add_to_waitlist(); }

Листинг 7-13. Приведение функции add_to_waitlist в область действия с помощью use.

Хотя листинг 7-11 и 7-13 выполняют одну и ту же задачу, листинг 7-11 является идиоматическим способом привести функцию в область действия с помощью use. Приведение функции родительского модуля в область действия с помощью use означает, что мы должны указать родительский модуль при вызове функции. Указание родительского модуля, когда вызывается функция, дает понять что функция не определена локально, минимизируя повторение полного пути. Код в листинге 7-13 неясен в контексте того, где определена add_to_waitlist.

С другой стороны, когда приводят в область действия структуры, перечисления и другие элементы с помощью use, идиоматично указывать полный путь. Листинг 7-14 показывает идиоматический способ приведения структуры HashMap стандартной библиотеки в область действия двоичного крейта.

use std::collections::HashMap;

fn main() { let mut map = HashMap::new(); map.insert(1, 2); }

Листинг 7-14. Приведение HashMap в область действия идиоматическим способом.

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

Исключение из этой идиомы - если мы приводим в область действие два элемента с одинаковыми именами с помощью операторов use, потому что Rust не позволит это. Листинг 7-15 показывает, как приводить два типа Result в одну и ту же область действия, когда у них одинаковые имена, но разные родительские модули, и как к ним обращаться.

use std::fmt;
use std::io;

fn function1() -> fmt::Result { // -- вырезано -- }

fn function2() -> io::Result< ()> { // -- вырезано -- }

Листинг 7-15. Приведение двух типов с одинаковыми именами в одну и ту же область действия требует использования их родительских модулей.

Как вы можете видеть, использование родительских модулей позволяет различать два типа Result. Если вместо этого мы укажем std::fmt::Result и use std::io::Result, у нас получилось бы два типа Result в одной и той же области, и Rust не знал бы, какой из них где надо применять.

Предоставление новых имен ключевым словом as. Есть другое решение проблемы привода двух типов с одинаковым именем в одну и ту же область действия: после пути мы можем указать as и новое локальное имя, или псевдоним (alias) для типа. Листинг 7-16 показывает этот другой способ написать код из листинга 7-15, путем переименования двух типов Result с помощью as.

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result { // -- вырезано -- }

fn function2() -> IoResult< ()> { // -- вырезано -- }

Листинг 7-16. Переименование типа с ключевым словом as, когда тип приводится в область видимости.

Во втором операторе use мы выбрали новое имя IoResult для типа std::io::Result, которое не будет конфликтовать с Result из std::fmt, который мы также приводим в область видимости. Листинги 7-15 и 7-16 считаются идиоматическими, так что выбор за вами!

Повторный экспорт имен с pub use. Когда мы приводим имя в область видимости с помощью ключевого слова use, это имя доступно в новой области как приватное. Чтобы разрешить коду вызывать наш код со ссылкой на это имя, как если бы оно было определено в текущей области видимости кода, мы можем комбинировать друг с другом pub и use. Эта техника называется повторным экспортом (re-exporting), потому что мы приводим элемент в область видимости, однако также делаем его доступным для других, чтобы вносить его в свою область действия.

Листинг 7-17 показывает код в листинге 7-11 с use в корневом модуле, измененном на pub use.

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }

Листинг 7-17. Способ с помощью pub use сделать имя доступным для любого кода при использовании из новой области действия.

Перед этим изменением внешний код должен был вызвать функцию add_to_waitlist с использованием пути restaurant::front_of_house::hosting::add_to_waitlist(), что также потребовало бы, чтобы модуль front_of_house был помечен как pub. Теперь, когда этот pub use повторно экспортировал модуль hosting из корневого модуля, внешний код может теперь использовать вместо этого путь restaurant::hosting::add_to_waitlist().

Повторный экспорт полезен, когда внутренняя структура вашего кода отличается от того, как новые программисты вызывают ваш код, будут думать о домене. Например, в этой метафоре ресторана люди, запускающие ресторан, думают про части ресторана как о "фронте дома" и "бэке дома". Однако посетители ресторана вероятно не думают о частях ресторана в таком ключе. Вместе с pub use, мы можем написать наш код с одной структурой, но раскрыть другую структуру. Это делает нашу библиотеку хорошо организованной для программистов, вызывающих библиотеку. Мы рассмотрим другой пример pub use, и как это влияет на документацию нашего крейта в секции "Exporting a Convenient Public API with pub use" главы 14.

Использование внешних пакетов. В главе 2 мы программировали игру "угадай число" [3], где использовался внешний пакет rand для получения случайных чисел. Для использования rand в нашем проекте мы добавили эту строку в файл Cargo.toml:

rand = "0.8.5"

Добавление rand в качестве зависимости в файл Cargo.toml говорит Cargo, что надо загрузить пакет rand и любые зависимости из crates.io, и сделать rand доступным для нашего проекта.

Затем, чтобы привести определения rand в область действия нашего пакета, мы добавили строку use, начинающуюся с имени крейта rand, и перечислили элементы, которые мы хотим привести в область видимости. Вспомните, что в секции "Генерация случайного числа" главы 2, мы привели трейт Rng в область видимости и вызвали функцию rand::thread_rng:

use rand::Rng;

fn main() { let secret_number = rand::thread_rng().gen_range(1..=100); }

Члены комьюнити Rust сделали множество пакетов, доступных на crates.io, и чтобы поместить любой из них в ваш пакет, нужно выполнить одни и те же шаги: перечислить в файле Cargo.toml вашего пакета, и применить use для приведения элементов из их крейтов в текущую область действия.

Обратите внимание, что стандартная библиотека std это также крейт, который внешний для нашего пакета. Поскольку стандартная библиотека поставляется вместе с языком Rust, нам не нужно менять Cargo.toml для подключения std. Однако нам нужно ссылаться на неё при помощи use, чтобы привести элементы кода оттуда в нашу область действия пакета. Например, с HashMap мы должны использовать следующую строку:

use std::collections::HashMap;

Это абсолютный путь, начинающийся от std, имени крейта стандартной библиотеки.

Использование вложенных путей для подчистки длинных списков use. Если мы используем несколько элементов, определенных в одном и том же крейте или одном и том же модуле, перечисление каждого элемента на отдельной строке может привести к тому, что в наших файлах будет занято несколько вертикальных строк. Например, эти два оператора use мы должны были применить в игре из листинга 2-4 (см. [3]), чтобы привести элементы из std в область действия:

// -- вырезано --
use std::cmp::Ordering;
use std::io;
// -- вырезано --

Вместо этого мы можем применить вложенные пути, чтобы привести те же элементы в область действия, одной строкой. Мы делаем это путем указания общей части пут, затем двойное двоеточие, и далее внутри фигурных скобок перечисляем список различающихся путей, как показано в листинге 7-18.

// -- вырезано --
use std::{cmp::Ordering, io};
// -- вырезано --

Листинг 7-18. Указание вложенного пути, чтобы привести в область действия несколько элементов с одинаковым префиксом.

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

Мы можем использовать вложенный путь на любом уровне пути, которые полезны при комбинировании двух операторов use, у которых один и тот же путь верхнего уровня. Например, листинг 7-19 показывает два оператора use: один привносит в область видимости std::io, а другой std::io::Write.

use std::io;
use std::io::Write;

Листинг 7-19. Два оператора use, у которого начальная часть пути одинаковая.

Общая часть этих двух путей std::io, и это полная первая часть пути. Чтобы соединить эти 2 пути в одном операторе use, мы можем использовать self во вложенном пути, как показывает листинг 7-20.

use std::io::{self, Write};

Листинг 7-20. Комбинирование путей листинга 7-19 в один оператор use.

Эта строка приводит std::io и std::io::Write в текущую область видимости.

Оператор glob. Если мы хотим привести в область видимости все публичные элементы, определенные в пути, то можно указать этот путь, если вставить за ним оператор * (это оператор glob):

use std::collections::*;

Этот оператор use приводит все публичные элементы, определенные в std::collections, в текущую область видимости. Будьте осторожны, когда используете оператор glob! Glob может затруднить анализ, какие имена в области действия используются в вашей программе, когда они были определены.

Оператор glob часто используется, когда тестируется приведение всего что можно в тестируемый модуль; мы поговорим об этом в секции "Как писать тесты" главы 11. Оператор glob также иногда используется как часть шаблона prelude: см. документацию на стандартную библиотеку [5] для дополнительной информации по этому шаблону.

[Разделение модулей на отдельные файлы]

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

Например, начнем с кода в листинге 7-17, где есть несколько модулей ресторана. Мы извлечем эти модули в файлы вместо того, чтобы все модули были определены в файле crate root. В этом случае файл crate root находится в src/lib.rs, однако эта процедура также работает и с двоичными крейтами, где файл crate root это src/main.rs.

Сначала мы извлечем модуль front_of_house в его отдельный файл. Удалим код внутри фигурных скобок для модуля front_of_house, оставив только декларацию mod front_of_house;, так что src/lib.rs теперь содержит код, показанный в листинге 7-21. Обратите внимание, что это не скомпилируется, пока мы не создадим файл src/front_of_house.rs в листинге 7-22.

mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }

Листинг 7-21. Декларирование модуля, тело которого будет в отдельном файле src/front_of_house.rs.

Затем поместим удаленный код, который был в фигурных скобках, в новый файл src/front_of_house.rs, как показано в листинге 7-22. Компилятор знает, где находится этот файл, потому что он приходит через декларацию модуля в crate root под именем front_of_house.

pub mod hosting {
    pub fn add_to_waitlist() {}
}

Листинг 7-22. Определения внутри модуля front_of_house, сохраненные в файл src/front_of_house.rs.

Обратите внимание, что вам только надо загрузить файл с использованием декларации mod один раз в вашем дереве модулей. Как только компилятор узнает, что файл это часть проекта (и знает, где в дереве модулей он находится, потому что вы в соответствующее место вставили оператор mod), другие файлы в вашем проекте должны ссылаться на код загруженного файла с использованием пути к тому месту, где он был объявлен, как было описано выше в секции "Пути для ссылок на элементы в дереве модулей". Другими словами, оператор mod это не операция "include", которую вы могли видеть в других языках программирования.

Далее мы извлекаем модуль hosting в его собственный отдельный файл. Процесс немного отличается, потому что hosting это дочерний модуль для модуля front_of_house, но не для корневого модуля. Мы поместим файл для hosting в новую директорию, которую назовем для его родителей в дереве модулей, в этом случае src/front_of_house/.

Чтобы начать перемещать hosting, мы поменяем src/front_of_house.rs, чтобы он содержал только декларацию модуля hosting:

pub mod hosting;

Затем мы создаем директорию src/front_of_house, и в ней файл hosting.rs, чтобы в нем содержались определения, сделанные в модуле hosting:

pub fn add_to_waitlist() {}

Если мы вместо этого поместим hosting.rs в директорию src, то компилятор будет ожидать, что код hosting.rs находится в модуле hosting, декларированном в crate root, а не объявлен как дочерний для модуля front_of_house. Правила компилятора для того, какие файлы проверить для каких модулей, означают, что каталоги и модули более точно соответствуют дереву модулей.

До сих пор мы рассматривали наиболее идиоматические (т. е. традиционные для Rust) пути файлов, которые использует компилятор Rust, однако Rust также поддерживает более старый стиль путей файлов. Для модуля с именем front_of_house, декларированном в crate root, компилятор ожидает код этого модуля в следующих местах:

src/front_of_house.rs (это мы рассматривали).
src/front_of_house/mod.rs (более старый стиль, все еще поддерживаемый путь).

Для модуля с именем hosting, который является субмодулем (дочерним) для front_of_house, компилятор будет ожидать его код в следующих местах:

src/front_of_house/hosting.rs (это мы рассматривали).
src/front_of_house/hosting/mod.rs (более старый стиль, все еще поддерживаемый путь).

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

Основной недостаток стиля, где используются файлы с именем mod.rs, состоит в том, что ваш проект в таком варианте может содержать множество файлов mod.rs, что очень неудобно, когда вы одновременно открыли их в вашем текстовом редакторе.

Мы переместили код каждого модуля в отдельный файл, и дерево модулей остается прежним. Вызовы функции в eat_at_restaurant будут работать без каких-либо изменений, даже если определения живут в разных файлах. Эта техника позволяет перемещать модули в новые файлы по мере увеличения их размера.

Обратите внимание, что оператор pub use crate::front_of_house::hosting statement в файле src/lib.rs также не поменялся, и use не оказывает никакого влияния на то, какие файлы компилируются как часть крейта. Ключевое слово mod декларирует модули, и Rust ищет их код в файле с тем же именем, что и имя модуля.

[Общие выводы]

Rust позволяет вам разделить пакет на несколько крейтов, и разделить крейт на модули, так что вы можете ссылаться на элементы кода, определенные в одном модуле, из другого модуля. Вы можете делать это путем указания абсолютного или относительного пути. Эти пути могут приводить элементы в область видимости с помощью оператора use, и вы можете сокращенный путь для нескольких use, находящихся в области видимости. Код модуля по умолчанию приватный, однако мы можете сделать его определения публичными путем добавления ключевого слова pub.

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

[Ссылки]

1. Rust Managing Growing Projects with Packages, Crates, and Modules site:rust-lang.org.
2. Язык программирования Rust.
3. Rust: программирование игры - угадывание числа.
4. Rust API Guidelines site:rust-lang.github.io.
5. Rust Module std::prelude site:rust-lang.org.

 

Добавить комментарий


Защитный код
Обновить

Top of Page