Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Публикация крейта на Crates.io

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

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

Полезные документационные комментарии

Точная документация помогает другим понять, как и когда пользоваться вашими пакетами, так что время на неё стоит потратить. В главе 3 мы говорили, как комментировать код на Rust двумя косыми чертами, //. Но в Rust есть и особый вид комментариев для документации — так и называемые документационные комментарии (documentation comments), — из которых генерируется HTML-документация. В HTML показывается содержимое документационных комментариев к элементам публичного API — для программистов, которым интересно, как пользоваться вашим крейтом, а не как он устроен.

Документационные комментарии начинаются с трёх косых черт, ///, и поддерживают разметку Markdown для форматирования текста. Пишите их прямо перед элементом, который документируете. В листинге 14-1 — документационные комментарии к функции add_one из крейта my_crate.

Файл: src/lib.rs
/// Прибавляет единицу к переданному числу.
///
/// # Примеры
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
Листинг 14-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

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

В листинге 14-1 Markdown-заголовок # Examples создал в 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, документационные тесты поймают расхождение между примером и кодом!

Комментарии к содержащему элементу

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

Например, чтобы описать назначение крейта my_crate, в котором находится функция add_one, добавим в начало файла src/lib.rs документационные комментарии, начинающиеся с //!, как в листинге 14-2.

Файл: src/lib.rs
//! # Мой крейт
//!
//! `my_crate` — набор утилит, упрощающих некоторые
//! вычисления.

/// Прибавляет единицу к переданному числу.
// --пропуск--
///
/// # Примеры
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
Листинг 14-2: Документация крейта my_crate в целом

Заметьте, что после последней строки, начинающейся с //!, нет никакого кода. Раз комментарии начинаются с //!, а не с ///, мы документируем элемент, который содержит этот комментарий, а не элемент после него. В нашем случае это файл 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.

Файл: src/lib.rs
//! # Art
//!
//! Библиотека для моделирования понятий из мира искусства.

pub mod kinds {
    /// Основные цвета в цветовой модели RYB.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// Составные цвета в цветовой модели RYB.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Смешивает два основных цвета в равных долях
    /// и получает составной цвет.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        // --пропуск--
        unimplemented!();
    }
}
Листинг 14-3: Библиотека art, элементы которой разложены по модулям kinds и utils

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

Документация крейта `art` со списком модулей `kinds` и `utils`

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

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

Другому крейту, который зависит от этой библиотеки, понадобятся инструкции use, подключающие элементы из art с указанием текущей структуры модулей. В листинге 14-4 — пример крейта, который использует элементы PrimaryColor и mix из крейта art.

Файл: src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
Листинг 14-4: Крейт, использующий элементы крейта art с его внутренней структурой

Автору кода из листинга 14-4 пришлось выяснять, что PrimaryColor лежит в модуле kinds, а mix — в модуле utils. Структура модулей крейта art важна скорее тем, кто его разрабатывает, чем тем, кто им пользуется. Тому, кто пытается понять, как пользоваться крейтом art, внутренняя структура ничего полезного не говорит, а только запутывает: приходится выяснять, где что искать, и указывать имена модулей в инструкциях use.

Чтобы убрать внутреннюю организацию из публичного API, изменим код крейта art из листинга 14-3: добавим инструкции pub use, которые реэкспортируют элементы на верхний уровень, как в листинге 14-5.

Файл: src/lib.rs
//! # Art
//!
//! Библиотека для моделирования понятий из мира искусства.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    // --пропуск--
    /// Основные цвета в цветовой модели RYB.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// Составные цвета в цветовой модели RYB.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    // --пропуск--
    use crate::kinds::*;

    /// Смешивает два основных цвета в равных долях
    /// и получает составной цвет.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        SecondaryColor::Orange
    }
}
Листинг 14-5: Добавляем инструкции pub use для реэкспорта элементов

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

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

Рисунок 14-4: Главная страница документации art со списком реэкспортов

Пользователи крейта art по-прежнему могут видеть и использовать внутреннюю структуру из листинга 14-3, как в листинге 14-4, а могут пользоваться более удобной структурой из листинга 14-5, как в листинге 14-6.

Файл: src/main.rs
use art::PrimaryColor;
use art::mix;

fn main() {
    // --пропуск--
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
Листинг 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 и вставьте ключ, когда его попросят:

$ cargo login
abcdefghijklmnopqrstuvwxyz012345

Эта команда сообщит Cargo ваш API-токен и сохранит его локально в ~/.cargo/credentials.toml. Учтите: токен — секрет, никому его не показывайте. Если по какой-то причине вы всё-таки им с кем-то поделились, отзовите его и сгенерируйте новый на crates.io.

Метаданные нового крейта

Допустим, у вас есть крейт, который вы хотите опубликовать. Перед публикацией нужно добавить кое-какие метаданные в секцию [package] файла Cargo.toml крейта.

Крейту нужно уникальное имя. Пока вы работаете над крейтом локально, назвать его можно как угодно. Но имена крейтов на crates.io раздаются по принципу «кто первым встал, того и тапки». Если имя занято, никто другой опубликовать крейт с таким именем уже не сможет. Прежде чем публиковать крейт, поищите на сайте имя, которое хотите использовать. Если оно занято, придумайте другое и для публикации поменяйте поле name в секции [package] файла Cargo.toml:

Файл: 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 укажите имя этого файла в ключе license-file.

Советы о том, какая лицензия подойдёт вашему проекту, выходят за рамки этой книги. Многие в сообществе 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).

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

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