Публикация крейта на 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.

Сгенерированная HTML-документация для функции `add_one` в `my_crate`

Рисунок 14-1: HTML-документация для функции add_one

Часто используемые разделы

Мы использовали заголовок Markdown # Examples в листинге 14-1, чтобы создать раздел в HTML с заголовком "Examples". Вот другие разделы, которые авторы крейтов часто используют в своей документации:

  • Panics: Сценарии, в которых документируемая функция может вызвать панику (panic). Вызывающие функцию, которые не хотят, чтобы их программы паниковали, должны убедиться, что они не вызывают функцию в этих ситуациях.
  • 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, мы увидим, что тесты документации обнаруживают, что пример и код не синхронизированы друг с другом!

Комментарии для содержащих элементов

Стиль комментария документации //! добавляет документацию к элементу, который содержит комментарии, а не к элементам, следующим за комментариями. Мы обычно используем эти комментарии документации внутри корневого файла крейта (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
}

Обратите внимание, что после последней строки, начинающейся с //!, нет никакого кода. Поскольку мы начали комментарии с //! вместо ///, мы документируем элемент, содержащий этот комментарий, а не элемент, следующий за этим комментарием. В этом случае этим элементом является файл src/lib.rs, который является корнем крейта. Эти комментарии описывают весь крейт.

Когда мы запускаем cargo doc --open, эти комментарии отображаются на главной странице документации для my_crate над списком публичных элементов крейта, как показано на рисунке 14-2.

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

Сгенерированная HTML-документация с комментарием для всего крейта

Рисунок 14-2: Сгенерированная документация для my_crate, включая комментарий, описывающий крейт в целом

Экспорт удобного публичного API

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

В главе 7 мы рассмотрели, как сделать элементы публичными с помощью ключевого слова pub и как поместить элементы в область видимости с помощью ключевого слова use. Однако структура, которая имеет смысл для вас во время разработки крейта, может быть не очень удобной для ваших пользователей. Вы можете организовать свои структуры в иерархии, содержащей несколько уровней, но тогда люди, которые хотят использовать тип, определенный глубоко в иерархии, могут не знать о его существовании. Их также может раздражать необходимость писать use my_crate::some_module::another_module::UsefulType; вместо use my_crate::UsefulType;.

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

Например, допустим, мы создали библиотеку с именем art для моделирования художественных концепций. В этой библиотеке есть два модуля: модуль kinds, содержащий два перечисления 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!();
    }
}

На рисунке 14-3 показано, как будет выглядеть главная страница документации для этого крейта, сгенерированная cargo doc.

Сгенерированная документация для крейта `art`, которая перечисляет модули `kinds` и `utils`

Рисунок 14-3: Главная страница документации для art, которая перечисляет модули kinds и utils

Обратите внимание, что типы PrimaryColor и SecondaryColor не перечислены на главной странице, как и функция mix. Мы должны нажать kinds и utils, чтобы увидеть их.

Другой крейт, который зависит от этой библиотеки, будет нуждаться в операторах use, которые помещают элементы из art в область видимости, указывая currently определенную структуру модуля. В листинге 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);
}

Автор кода в листинге 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
    }
}

API-документация, которую cargo doc генерирует для этого крейта, теперь будет перечислять и ссылаться на повторно экспортируемые элементы на главной странице, как показано на рисунке 14-4, что облегчает поиск типов PrimaryColor и SecondaryColor и функции mix.

Сгенерированная документация для крейта `art` с повторно экспортированными элементами на главной странице

Рисунок 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);
}

В случаях, когда есть много вложенных модулей, повторный экспорт типов на верхний уровень с помощью 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 вам нужно указать значение идентификатора лицензии. На Сайте обмена данными о программных пакетах (SPDX) Фонда Линукса перечислены идентификаторы, которые вы можете использовать для этого значения. Например, чтобы указать, что вы лицензировали свой крейт с помощью лицензии 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, и публикуете заново. Используйте правила Семантического Версионирования (Semantic Versioning), чтобы решить, какой следующий номер версии является подходящим, основываясь на типах внесенных вами изменений. Затем запустите cargo publish, чтобы загрузить новую версию.

Устаревание версий на Crates.io

Хотя вы не можете удалить предыдущие версии крейта, вы можете предотвратить добавление их в качестве новых зависимостей любыми будущими проектами. Это полезно, когда версия крейта по той или иной причине сломана. В таких ситуациях Cargo поддерживает отзыв (yanking) версии крейта.

Отзыв версии предотвращает зависимость новых проектов от этой версии, позволяя при этом всем существующим проектам, которые зависят от нее, продолжать работу. По сути, отзыв означает, что все проекты с файлом 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 к команде, вы также можете отменить отзыв и разрешить проектам снова начинать зависеть от версии:

$ cargo yank --vers 1.0.1 --undo
    Updating crates.io index
      Unyank guessing_game@1.0.1

Отзыв не удаляет никакой код. Он не может, например, удалить случайно загруженные секреты. Если это произойдет, вы должны немедленно сбросить эти секреты.