Публикация крейта на Crates.io
Мы использовали пакеты с crates.io как зависимости нашего проекта, но вы также можете делиться своим кодом с другими людьми, публикуя собственные пакеты. Реестр крейтов на crates.io распространяет исходный код ваших пакетов, поэтому в основном на нем размещают код с открытым исходным кодом.
В Rust и Cargo есть возможности, которые помогают людям легче находить и использовать опубликованный вами пакет. Далее мы поговорим о некоторых из этих возможностей, а затем объясним, как опубликовать пакет.
Создание полезных комментариев документации
Точное документирование ваших пакетов поможет другим пользователям понять, как
и когда их использовать, поэтому стоит потратить время на написание
документации. В главе 3 мы обсуждали, как комментировать код Rust с помощью
двух косых черт, //. В Rust также есть особый вид комментариев для
документации, удобно называемый комментарием документации, который генерирует
HTML-документацию. HTML отображает содержимое комментариев документации для
элементов публичного API, предназначенное для программистов, которым интересно
узнать, как использовать ваш крейт, а не как он реализован.
Комментарии документации используют три косые черты, ///, вместо двух и
поддерживают синтаксис Markdown для форматирования текста. Размещайте
комментарии документации прямо перед элементом, который они документируют. В
листинге 14-1 показаны комментарии документации для функции add_one в крейте
с именем my_crate.
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
Здесь мы даем описание того, что делает функция add_one, начинаем раздел с
заголовком Examples, а затем приводим код, показывающий, как использовать
функцию add_one. Мы можем сгенерировать HTML-документацию из этого
комментария документации, запустив cargo doc. Эта команда запускает
инструмент rustdoc, поставляемый вместе с Rust, и помещает сгенерированную
HTML-документацию в каталог target/doc.
Для удобства запуск cargo doc --open соберет HTML для документации текущего
крейта (а также документацию всех зависимостей вашего крейта) и откроет
результат в веб-браузере. Перейдите к функции add_one, и вы увидите, как
отображается текст из комментариев документации, как показано на рисунке 14-1.
Рисунок 14-1: HTML-документация для функции
add_one
Часто используемые разделы
Мы использовали Markdown-заголовок # Examples в листинге 14-1, чтобы создать
раздел в HTML с названием “Examples.” Вот некоторые другие разделы, которые
авторы крейтов часто используют в своей документации:
- Panics: сценарии, в которых документируемая функция может запаниковать. Вызывающий код, который не хочет, чтобы его программа паниковала, должен убедиться, что не вызывает функцию в таких ситуациях.
- Errors: если функция возвращает
Result, описание видов ошибок, которые могут возникнуть, и условий, которые могут привести к возврату этих ошибок, может быть полезно вызывающему коду, чтобы он мог писать код для обработки разных видов ошибок разными способами. - Safety: если вызов функции является
unsafe(мы обсудим небезопасность в главе 20), должен быть раздел, объясняющий, почему функция небезопасна, и описывающий инварианты, которые функция ожидает от вызывающего кода.
Большинству комментариев документации не нужны все эти разделы, но это хороший контрольный список, который напоминает, о каких аспектах вашего кода пользователи захотят знать.
Комментарии документации как тесты
Добавление блоков с примером кода в комментарии документации может помочь
показать, как использовать вашу библиотеку, и дает дополнительный бонус: запуск
cargo test будет запускать примеры кода из вашей документации как тесты!
Нет ничего лучше документации с примерами. Но нет ничего хуже примеров, которые
не работают, потому что код изменился после написания документации. Если мы
запустим cargo test с документацией для функции add_one из листинга 14-1,
то увидим в результатах тестов раздел, похожий на этот:
Doc-tests my_crate
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
Теперь, если мы изменим либо функцию, либо пример так, что assert_eq! в
примере запаникует, и снова запустим cargo test, мы увидим, что
документационные тесты обнаружат рассинхронизацию между примером и кодом!
Комментарии для содержащих элементов
Стиль комментария документации //! добавляет документацию к элементу, который
содержит комментарии, а не к элементам, следующим за комментариями. Обычно
мы используем такие doc-комментарии внутри корневого файла крейта (src/lib.rs
по соглашению) или внутри модуля, чтобы документировать крейт или модуль в
целом.
Например, чтобы добавить документацию, описывающую назначение крейта
my_crate, который содержит функцию add_one, мы добавляем комментарии
документации, начинающиеся с //!, в начало файла src/lib.rs, как показано
в листинге 14-2.
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
my_crate в целомОбратите внимание, что после последней строки, начинающейся с //!, нет кода.
Поскольку мы начали комментарии с //!, а не с ///, мы документируем
элемент, который содержит этот комментарий, а не элемент, следующий за этим
комментарием. В данном случае этот элемент – файл src/lib.rs, являющийся
корнем крейта. Эти комментарии описывают весь крейт.
Когда мы запускаем cargo doc --open, эти комментарии отображаются на главной
странице документации для my_crate над списком публичных элементов крейта,
как показано на рисунке 14-2.
Комментарии документации внутри элементов особенно полезны для описания крейтов и модулей. Используйте их, чтобы объяснить общее назначение контейнера и помочь пользователям понять организацию крейта.
Рисунок 14-2: Отрисованная документация для my_crate,
включающая комментарий, описывающий крейт в целом
Экспорт удобного публичного API
Структура вашего публичного API – важное соображение при публикации крейта. Люди, использующие ваш крейт, знакомы с его структурой хуже вас и могут столкнуться с трудностями при поиске нужных им частей, если у вашего крейта большая иерархия модулей.
В главе 7 мы рассмотрели, как делать элементы публичными с помощью ключевого
слова pub и как вводить элементы в область видимости с помощью ключевого
слова use. Однако структура, которая кажется вам разумной во время
разработки крейта, может быть не очень удобной для ваших пользователей.
Возможно, вы захотите организовать структуры в иерархию из нескольких уровней,
но тогда людям, которые хотят использовать тип, определенный глубоко в
иерархии, может быть трудно узнать, что этот тип существует. Их также может
раздражать необходимость писать use my_crate::some_module::another_module::UsefulType; вместо use my_crate::UsefulType;.
Хорошая новость в том, что если структура неудобна для использования из
другой библиотеки, вам не нужно перестраивать внутреннюю организацию:
вместо этого вы можете реэкспортировать элементы, чтобы создать публичную
структуру, отличающуюся от вашей приватной структуры, с помощью pub use.
Реэкспорт берет публичный элемент в одном месте и делает его публичным в
другом месте, как если бы он был определен там.
Например, допустим, мы создали библиотеку с именем art для моделирования
художественных понятий. Внутри этой библиотеки есть два модуля: модуль kinds,
содержащий два enum с именами PrimaryColor и SecondaryColor, и модуль
utils, содержащий функцию с именем mix, как показано в листинге 14-3.
//! # 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--
unimplemented!();
}
}
art с элементами, организованными в модули kinds и utilsНа рисунке 14-3 показано, как выглядела бы главная страница документации для
этого крейта, сгенерированная cargo doc.
Рисунок 14-3: Главная страница документации для art,
где перечислены модули kinds и utils
Обратите внимание, что типы PrimaryColor и SecondaryColor не перечислены на
главной странице, как и функция mix. Чтобы увидеть их, нужно щелкнуть
kinds и utils.
Другому крейту, который зависит от этой библиотеки, понадобятся инструкции
use, вводящие элементы из art в область видимости и указывающие текущую
структуру модулей. В листинге 14-4 показан пример крейта, который использует
элементы PrimaryColor и mix из крейта art.
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art с экспортированной внутренней структуройАвтору кода в листинге 14-4, который использует крейт art, пришлось выяснить,
что PrimaryColor находится в модуле kinds, а mix – в модуле utils.
Структура модулей крейта art более важна для разработчиков, работающих над
крейтом art, чем для тех, кто его использует. Внутренняя структура не
содержит полезной информации для человека, который пытается понять, как
использовать крейт art; скорее она вызывает путаницу, потому что
разработчикам, использующим крейт, приходится выяснять, где искать, и
указывать имена модулей в инструкциях use.
Чтобы убрать внутреннюю организацию из публичного API, мы можем изменить код
крейта art из листинга 14-3 и добавить инструкции pub use, чтобы
реэкспортировать элементы на верхнем уровне, как показано в листинге 14-5.
//! # 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--
/// 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 {
// --snip--
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Orange
}
}
pub use для реэкспорта элементовДокументация API, которую cargo doc сгенерирует для этого крейта, теперь
будет перечислять реэкспорты на главной странице и давать ссылки на них, как
показано на рисунке 14-4, благодаря чему типы PrimaryColor и SecondaryColor
и функцию mix будет проще найти.
Рисунок 14-4: Главная страница документации для art,
где перечислены реэкспорты
Пользователи крейта art все еще могут видеть и использовать внутреннюю
структуру из листинга 14-3, как показано в листинге 14-4, или могут
использовать более удобную структуру из листинга 14-5, как показано в листинге
14-6.
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
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 login
abcdefghijklmnopqrstuvwxyz012345
Эта команда сообщит Cargo ваш API-токен и сохранит его локально в ~/.cargo/credentials.toml. Обратите внимание, что этот токен является секретом: не делитесь им ни с кем. Если вы по какой-либо причине поделились им с кем-то, нужно отозвать его и сгенерировать новый токен на crates.io.
Добавление метаданных в новый крейт
Допустим, у вас есть крейт, который вы хотите опубликовать. Перед публикацией
нужно добавить некоторые метаданные в раздел [package] файла Cargo.toml
этого крейта.
Вашему крейту понадобится уникальное имя. Пока вы работаете над крейтом
локально, можете назвать его как угодно. Однако имена крейтов на
crates.io выдаются в порядке очереди.
Когда имя крейта уже занято, никто другой не сможет опубликовать крейт с этим
именем. Перед попыткой опубликовать крейт выполните поиск по имени, которое
хотите использовать. Если имя уже использовано, нужно найти другое имя и
изменить поле name в файле Cargo.toml в разделе [package], чтобы
использовать новое имя для публикации, например так:
Имя файла: Cargo.toml
[package]
name = "guessing_game"
Даже если вы выбрали уникальное имя, при запуске cargo publish для публикации
крейта на этом этапе вы получите предупреждение, а затем ошибку:
$ 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 (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields
Это приводит к ошибке, потому что отсутствует важная информация: описание и
лицензия обязательны, чтобы люди знали, что делает ваш крейт и на каких
условиях они могут его использовать. В Cargo.toml добавьте описание всего в
одно-два предложения, потому что оно появится вместе с вашим крейтом в
результатах поиска. Для поля license нужно указать значение идентификатора
лицензии. Software Package Data Exchange (SPDX) от Linux Foundation
перечисляет идентификаторы, которые можно использовать для этого значения.
Например, чтобы указать, что вы лицензировали крейт с использованием лицензии
MIT, добавьте идентификатор MIT:
Имя файла: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
Если вы хотите использовать лицензию, которой нет в SPDX, нужно поместить
текст этой лицензии в файл, включить файл в проект, а затем использовать
license-file, чтобы указать имя этого файла вместо ключа license.
Рекомендации о том, какая лицензия подходит вашему проекту, выходят за рамки
этой книги. Многие люди в сообществе Rust лицензируют свои проекты так же, как
Rust, используя двойную лицензию MIT OR Apache-2.0. Эта практика показывает,
что вы также можете указать несколько идентификаторов лицензий, разделенных
OR, чтобы у проекта было несколько лицензий.
После добавления уникального имени, версии, описания и лицензии файл Cargo.toml для проекта, готового к публикации, может выглядеть так:
Имя файла: Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
Документация Cargo описывает другие метаданные, которые можно указать, чтобы другим было проще находить и использовать ваш крейт.
Публикация на Crates.io
Теперь, когда вы создали учетную запись, сохранили API-токен, выбрали имя для крейта и указали обязательные метаданные, вы готовы к публикации! Публикация крейта загружает конкретную версию на crates.io, чтобы другие могли ее использовать.
Будьте осторожны, потому что публикация постоянна. Версию нельзя перезаписать, а код нельзя удалить, кроме некоторых обстоятельств. Одна из главных целей Crates.io – быть постоянным архивом кода, чтобы сборки всех проектов, зависящих от крейтов с crates.io, продолжали работать. Разрешение удалять версии сделало бы выполнение этой цели невозможным. Однако количество версий крейта, которые вы можете опубликовать, не ограничено.
Снова выполните команду cargo publish. Теперь она должна завершиться успешно:
$ cargo publish
Updating crates.io index
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Packaged 6 files, 1.2KiB (895.0B compressed)
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` profile [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
Uploaded guessing_game v0.1.0 to registry `crates-io`
note: waiting for `guessing_game v0.1.0` to be available at registry
`crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
Published guessing_game v0.1.0 at registry `crates-io`
Поздравляем! Теперь вы поделились своим кодом с сообществом Rust, и любой сможет легко добавить ваш крейт как зависимость своего проекта.
Публикация новой версии существующего крейта
Когда вы внесли изменения в крейт и готовы выпустить новую версию, измените
значение version, указанное в файле Cargo.toml, и опубликуйте крейт снова.
Используйте правила семантического версионирования, чтобы решить,
какой следующий номер версии подходит с учетом видов внесенных изменений.
Затем выполните cargo publish, чтобы загрузить новую версию.
Вывод версий из использования на Crates.io
Хотя вы не можете удалить предыдущие версии крейта, вы можете запретить будущим проектам добавлять их как новую зависимость. Это полезно, когда версия крейта по той или иной причине сломана. В таких ситуациях Cargo поддерживает вывод версии крейта из использования, или yank.
Yank версии не дает новым проектам зависеть от этой версии, но позволяет всем существующим проектам, которые уже зависят от нее, продолжать работу. По сути, yank означает, что все проекты с Cargo.lock не сломаются, а любые будущие файлы Cargo.lock не будут использовать выведенную из использования версию.
Чтобы вывести версию крейта из использования, в каталоге ранее опубликованного
крейта выполните cargo yank и укажите версию, которую хотите вывести. Например,
если мы опубликовали крейт с именем guessing_game версии 1.0.1 и хотим
вывести ее из использования, то в каталоге проекта guessing_game выполним
следующее:
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
Добавив к команде --undo, вы также можете отменить yank и снова разрешить
проектам начинать зависеть от этой версии:
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
Yank не удаляет никакой код. Например, он не может удалить случайно загруженные секреты. Если это произошло, нужно немедленно сбросить эти секреты.