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

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

Filename: src/lib.rs
/// 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
}
Listing 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

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

Мы использовали заголовок 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, мы увидим, что документационные тесты обнаруживают, что пример и код не синхронизированы друг с другом!

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

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

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

Filename: src/lib.rs
//! # 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
}
Listing 14-2: Документация для крейта my_crate в целом

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

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

Отрендеренная документация с комментарием для крейта в целом

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

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

Экспорт удобного публичного API с помощью pub use

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

В Главе 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:

Filename: src/lib.rs
//! # 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!();
    }
}
Listing 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:

Filename: src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;

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

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

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

Filename: src/lib.rs
//! # 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
    }
}
Listing 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:

Filename: src/main.rs
use art::PrimaryColor;
use art::mix;

fn main() {
    // --snip--
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
Listing 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 login
abcdefghijklmnopqrstuvwxyz012345

Эта команда сообщит Cargo ваш API-токен и сохранит его локально в ~/.cargo/credentials. Обратите внимание, что этот токен является секретом: не делитесь им с кем-либо еще. Если вы все же поделитесь им по какой-либо причине, вы должны отозвать его и сгенерировать новый токен на 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 перечислены идентификаторы, которые вы можете использовать для этого значения. Например, чтобы указать, что вы лицензировали свой крейт, используя лицензию 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)
   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)

Поздравляем! Теперь вы поделились своим кодом с сообществом Rust, и любой может легко добавить ваш крейт в качестве зависимости своего проекта.

Публикация новой версии существующего крейта

Когда вы внесли изменения в свой крейт и готовы выпустить новую версию, вы изменяете значение version, указанное в вашем файле Cargo.toml, и снова публикуете. Используйте правила семантического версионирования, чтобы решить, какое подходящее следующее номер версии на основе внесенных изменений. Затем запустите cargo publish, чтобы загрузить новую версию.

Устаревание версий на Crates.io с помощью cargo yank

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

Отзыв (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

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