Рисунок 14-2: Предоставленная документация для
my_crate
, включая комментарий, описывающие крейт в
целом
Комментарии к документации внутри элементов полезны для описания крейтов и модулей особенно. Используйте их, чтобы объяснить общую цель контейнера, чтобы помочь вашим пользователям понять организацию крейта.
Экспорт удобного общедоступного API с pub use
Структура вашего публичного API является основным фактором при публикации крейта.
Люди, которые используют вашу библиотеку, менее знакомы со структурой, чем вы и
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
могут столкнуться с трудностями при поиске частей, которые они хотят использовать,
если ваша библиотека имеет большую иерархию модулей.
В главе 7 мы рассмотрели, как сделать элементы общедоступными с помощью ключевого слова pub и ввести элементы в область видимости с помощью ключевого слова use
Однако структура, которая имеет смысл для вас при разработке крейта, может быть не очень удобной для пользователей. Вы можете организовать структуру в виде иерархии с несколькими уровнями, но тогда люди, желающие использовать тип, который вы определили в глубине иерархии, могут столкнуться с проблемой его поиска. Их также может раздражать необходимость вводить use my_crate::some_module::another_module::UsefulType;
вместо use my_crate::UsefulType;
Хорошей новостью является то, что если структура не удобна для использования другими из другой библиотеки, вам не нужно перестраивать внутреннюю организацию: вместо этого вы можете реэкспортировать элементы, чтобы сделать публичную структуру,
отличную от вашей внутренней структуры, используя pub use
. Реэкспорт берет открытый элемент в одном месте и делает его публичным в другом месте, как если бы он был определён в другом месте.
Например, скажем, мы создали библиотеку с именем art для моделирования художественных концепций. Внутри этой библиотеки есть два модуля: модуль kinds содержащий два перечисления с именами
PrimaryColor и
SecondaryColor и модуль utils
, содержащий функцию с именем mix
, как показано в листинге 14-3:
Файл: src/lib.rs
Листинг 14-3: Библиотека
art
с элементами, организованными в модули
kinds
и
utils
На рисунке 14-3 показано, как будет выглядеть титульная страница документации для этого крейта, сгенерированный cargo doc
:
//! # Art
//!
//! A library for modeling artistic concepts.
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum
PrimaryColor
{
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum
SecondaryColor
{
Orange,
Green,
Purple,
}
} pub mod utils { use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix
(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
}
}
Рисунок 14-3: Первая страница документации для
art
, в которой перечислены модули
kinds
и
utils
Обратите внимание, что типы
PrimaryColor и
SecondaryColor не указаны на главной странице, равно как и функция mix
. Мы должны нажать kinds и utils
, чтобы увидеть их.
В другой библиотеке, которая зависит от этой библиотеки, потребуются операторы use
,
которые подключают элементы из art в область видимости, определяя структуру модуля, которая определена в данный момент. В листинге 14-4 показан пример крейта, в котором используются элементы
PrimaryColor и mix из крейта art
:
Файл: src/main.rs
Листинг 14-4: Крейт использующий элементы из крейта
art
с экспортированной внутренней структурой
Автору кода в листинге 14-4, который использует крейт art
, пришлось выяснить, что
PrimaryColor находится в модуле kinds
, а mix
- в модуле utils
. Структура модуля art крейта больше подходит для разработчиков, работающих над art крейтом, чем для тех,
кто его использует. Внутренняя структура не содержит никакой полезной информации для того, кто пытается понять, как использовать крейт art
, а скорее вызывает путаницу,
поскольку разработчики, использующие его, должны понять, где искать, и должны указывать имена модулей в выражениях use
Чтобы удалить внутреннюю организацию из общедоступного API, мы можем изменить код крейта art в листинге 14-3, чтобы добавить операторы pub use для повторного реэкспорта элементов на верхнем уровне, как показано в листинге 14-5:
Файл: src/lib.rs use art::kinds::PrimaryColor; use art::utils::mix; fn main
() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow);
}
Листинг 14-5: Добавление операторов
pub use
для реэкспорта элементов
Документация API, которую cargo doc генерирует для этой библиотеки, теперь будет перечислять и связывать реэкспорты на главной странице, как показано на рисунке 14-4,
упрощая поиск типов
PrimaryColor
,
SecondaryColor и функции mix
Рисунок 14-4: Первая страница документации для
art
, которая перечисляет реэкспорт
Пользователи крейта art могут по-прежнему видеть и использовать внутреннюю структуру из листинга 14-3, как показано в листинге 14-4, или они могут использовать
//! # Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor; pub use self::kinds::SecondaryColor; pub use self::utils::mix; pub mod kinds {
// --snip--
} pub mod utils {
// --snip--
}
более удобную структуру в листинге 14-5, как показано в листинге 14-6:
Файл: src/main.rs
Листинг 14-6: Программа, использующая реэкспортированные элементы из крейта art
В случаях, когда имеется много вложенных модулей, реэкспорт типов на верхнем уровне с помощью pub use может существенно повысить удобство работы для людей,
использующих крейт. Ещё одно распространённое использование pub use
- это реэкспорт определений зависимого модуля в текущем крейте, чтобы сделать определения этого крейта частью публичного API вашего крейта.
Создание полезной публичной структуры API - это больше искусство чем наука, и вы можете повторять, чтобы найти API, который лучше всего подойдёт вашим пользователям. Использование pub use даёт вам гибкость в том, как вы структурируете свою библиотеку внутри и отделяете эту внутреннюю структуру от того, что вы предоставляете пользователям. Посмотрите на код некоторых установленных крейтов,
чтобы увидеть отличается ли их внутренняя структура от их публичного API.
Настройка учётной записи Crates.ioПрежде чем вы сможете опубликовать любые библиотеки, вам необходимо создать учётную запись на crates.io и получить API токен. Для этого зайдите на домашнюю страницу crates.io и войдите в систему через учётную запись GitHub. (В настоящее время требуется наличие учётной записи GitHub, но сайт может поддерживать другие способы создания учётной записи в будущем.) Сразу после входа в систему перейдите в настройки своей учётной записи по адресу https://crates.io/me/
и получите свой ключ API. Затем выполните команду cargo login с вашим ключом API, например:
Эта команда сообщит Cargo о вашем API token и сохранит его локально в
/.cargo/credentials. Обратите внимание, что этот токен является
секретным: не делитесь им ни с кем другим. Если вы по какой-либо
причине поделитесь им с кем-либо, вы должны отозвать его и сгенерировать новый токен на crates.io
Добавление метаданных в новую библиотекуuse art::mix; use art::PrimaryColor; fn main
() {
// --snip--
}
$
cargo login abcdefghijklmnopqrstuvwxyz012345
Допустим, у вас есть крейт, который вы хотите опубликовать. Перед публикацией вам нужно добавить некоторые метаданные в раздел
[package]
файла Cargo.toml крейта.
Вашему крейту понадобится уникальное имя. Пока вы работаете над крейтом локально,
вы можете назвать его как угодно. Однако названия крейтов на crates.io фиксируются в момент первой публикации. Как только крейту присвоено название, никто другой не сможет опубликовать крейт с таким же именем. Перед тем как опубликовать крейт,
поищите название, которое вы хотите использовать. Если такое имя уже используется,
вам придётся подобрать другое и отредактировать поле name в файле Cargo.toml в разделе
[package]
, чтобы использовать новое имя в качестве публикуемого, например,
так:
Файл: Cargo.toml
Даже если вы выбрали уникальное имя, когда вы запустите cargo publish чтобы опубликовать крейт, вы получите предупреждение, а затем ошибку:
Это ошибка, потому что вам не хватает важной информации: необходимы описание и лицензия, чтобы люди знали, что делает ваш крейт и на каких условиях они могут его использовать. В поле Cargo.toml добавьте описание, состоящее из одного-двух предложений, поскольку оно будет появляться вместе с вашим крейтом в результатах поиска. Для поля license нужно указать значение идентификатора лицензии. В
Linux
Foundation's Software Package Data Exchange (SPDX)
перечислены идентификаторы,
которые можно использовать для этого значения. Например, чтобы указать, что вы лицензировали свой crate, используя лицензию MIT, добавьте идентификатор
MIT
:
Файл: Cargo.toml
[package]
name =
"guessing_game"
$
cargo publish
Updating crates.io index warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip-- error: failed to publish to registry at https://crates.io
Caused by: the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust- lang.org/cargo/reference/manifest.html for how to upload metadata
[package]
name =
"guessing_game"
license =
"MIT"
Если вы
хотите использовать лицензию, которая отсутствует в SPDX, вам нужно поместить текст этой лицензии в файл, включите файл в свой проект, а затем используйте license-file
, чтобы указать имя этого файла вместо использования ключа license
Руководство по выбору лицензии для вашего проекта выходит за рамки этой книги.
Многие люди в сообществе Rust лицензируют свои проекты так же, как и Rust, используя двойную лицензию
MIT OR Apache 2.0
. Эта практика демонстрирует, что вы также можете указать несколько идентификаторов лицензий, разделённых
OR
, чтобы иметь несколько лицензий для вашего проекта.
С добавлением уникального имени, версии, вашего описания и лицензии, файл
Cargo.toml для проекта, который готов к публикации может выглядеть следующим образом:
Файл: Cargo.toml
Документация Cargo описывает другие метаданные, которые вы можете указать, чтобы другие могли легче находить и использовать ваш крейт.
Публикация на Crates.ioТеперь, когда вы создали учётную запись, сохранили свой токен API, выбрали имя для своего крейта и указали необходимые метаданные, вы готовы к публикации! Публикация библиотеки загружает определённую версию в crates.io для использования другими.
Будьте осторожны, потому что публикация является
перманентной операцией. Версия никогда не сможет быть перезаписана, а код не подлежит удалению. Одна из основных целей crates.io
- служить постоянным архивом кода, чтобы сборки всех проектов,
зависящих от crates из crates.io продолжали работать. Предоставление возможности удаления версий сделало бы выполнение этой цели невозможным. При этом количество версий крейтов, которые вы можете опубликовать, не ограничено.
Запустите команду cargo publish ещё раз. Сейчас эта команда должна выполниться успешно:
[package]
name =
"guessing_game"
version =
"0.1.0"
edition =
"2021"
description =
"A fun game where you guess what number the computer has chosen."
license =
"MIT OR Apache-2.0"
[dependencies]
Поздравляем! Теперь вы поделились своим кодом с сообществом Rust и любой может легко добавить вашу библиотеку в качестве зависимости их проекта.
Публикация новой версии существующей библиотекиКогда вы внесли изменения в свой крейт и готовы выпустить новую версию, измените значение version
, указанное в вашем файле
Cargo.toml и повторите публикацию.
Воспользуйтесь
Semantic Versioning rules
, чтобы решить, какой номер следующей версии подходит для ваших изменений. Затем запустите cargo publish
, чтобы загрузить новую версию.
Устранение устаревших версий с Crates.io с помощью cargo yank
Хотя вы не можете удалить предыдущие версии крейта, вы можете помешать любым будущим проектам добавлять его в качестве новой зависимости. Это полезно, когда версия крейта сломана по той или иной причине. В таких ситуациях Cargo поддерживает
выламывание (yanking) версии крейта.
Вычёркивание версии не позволяет новым проектам зависеть от этой версии, но при этом позволяет всем существующим проектам, зависящим от неё, продолжать работу. По сути, исключение означает, что все проекты с
Cargo.lock не сломаются, а любые файлы
Cargo.lock, которые будут генерироваться в будущем, не смогут использовать исключённую версию.
Чтобы вычеркнуть версию крейта, в директории крейта, который вы опубликовали ранее, выполните команду cargo yank и укажите, какую версию вы хотите вычеркнуть.
Например, если мы опубликовали крейт под названием guessing_game версии 1.0.1 и хотим вычеркнуть её, в каталоге проекта для guessing_game мы выполним:
Добавив в команду
--undo
, вы также можете отменить выламывание и разрешить проектам начать зависеть от версии снова:
$
cargo publish
Updating crates.io index
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished dev [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
$
cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game:1.0.1
Вычёркивание не удаляет код. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли.
$
cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game_:1.0.1
Рабочие пространства CargoВ главе 12 мы создали пакет, который включал в себя бинарный и библиотечный крейты.
По мере развития вашего проекта может возникнуть ситуация, когда библиотечный крейт будет становиться все больше, и вы захотите разделить ваш пакет на несколько библиотечных крейтов. Cargo предоставляет функциональность под названием
workspaces, которая помогает управлять несколькими взаимосвязанными пакетами,
которые разрабатываются в тандеме.
Создание рабочего пространстваWorkspace - это набор пакетов, которые используют один и тот же
Cargo.lock и директорию для хранения результатов компиляции. Давайте создадим проект с использованием
workspace - мы будем использовать тривиальный код, чтобы сосредоточиться на структуре рабочего пространства. Существует несколько способов структурировать рабочую область, но мы покажем только один из них. У нас
будет рабочая область,
содержащая двоичный файл и две библиотеки. Двоичный файл, который обеспечивает основную функциональность, будет зависеть от двух библиотек. Одна библиотека предоставит функцию add_one
, а вторая - add_two
. Эти три крейта будут частью одного
workspace. Начнём с создания каталога для рабочего окружения:
Далее в каталоге
add мы создадим файл
Cargo.toml, который будет определять конфигурацию всего рабочего окружения. В этом файле не будет секции
[package]
Вместо этого он будет начинаться с секции
[workspace]
, которая позволит нам добавить модули в рабочее пространство, указав путь к пакету с нашим бинарным крейтом; в данном случае этот путь -
adder:
Файл: Cargo.toml
Затем мы создадим исполняемый крейт adder
, запустив команду cargo new в каталоге
add:
На этом этапе мы можем создать рабочее пространство, запустив команду cargo build
Файлы в каталоге
add должны выглядеть следующим образом:
$
mkdir add
$
cd add
[workspace]
members = [
"adder"
,
]
$
cargo new adder
Created binary (application) `adder` package
Рабочая область содержит на верхнем уровне один каталог
target, в который будут помещены скомпилированные артефакты; пакет adder не имеет собственного каталога
target. Даже если мы запустим cargo build из каталога
adder, скомпилированные артефакты все равно окажутся в
add/target, а не в
add/adder/target. Cargo так определил директорию
target в рабочем пространстве, потому что крейты в рабочем пространстве должны зависеть друг от друга. Если бы каждый крейт имел свой собственный каталог
target, каждому крейту пришлось бы перекомпилировать каждый из других крейтов в рабочем пространстве, чтобы поместить артефакты в свой собственный каталог
target.
Благодаря совместному использованию единого каталога
target крейты могут избежать ненужной перекомпиляции.
Добавление второго крейта в рабочее пространствоДалее давайте создадим ещё одного участника пакета в рабочей области и назовём его add_one
. Внесите изменения в
Cargo.toml верхнего уровня так, чтобы указать путь
add_one в списке members
:
Файл: Cargo.toml
Затем сгенерируйте новый крейт библиотеки с именем add_one
:
Ваш каталог
add должен теперь иметь следующие каталоги и файлы:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
[workspace]
members = [
"adder"
,
"add_one"
,
]
$
cargo new add_one --lib
Created library `add_one` package
В файле
add_one/src/lib.rs добавим функцию add_one
:
Файл: add_one/src/lib.rs
Теперь мы можем сделать так, чтобы пакет adder с нашим исполняемым файлом зависел от пакета add_one
, содержащего нашу библиотеку. Сначала нам нужно добавить зависимость пути от add_one в
adder/Cargo.toml.
Файл: adder/Cargo.toml
Cargo не исходит из того, что крейты в рабочем пространстве могут зависеть друг от друга, поэтому нам необходимо явно указать отношения зависимости.
Далее, давайте используем функцию add_one
(из крейта add_one
) в крейте adder
Откройте файл
adder/src/main.rs и добавьте строку use в верхней части, чтобы ввести в область видимости новый библиотечный крейт add_one
. Затем измените функцию main для вызова функции add_one
, как показано в листинге 14-7.
Файл: adder/src/main.rs