Программирование PC Rust: дополнительная информация про Cargo и Crates.io Mon, September 16 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Rust: дополнительная информация про Cargo и Crates.io Печать
Добавил(а) microsin   

Пока что мы использовали только базовые функции Cargo для сборки (cargo build), сборки и запуска (cargon run), а также для тестирования нашего кода (cargo test), но cargo может много больше. В этой главе (перевод главы 14 документации [1]) мы обсудим некоторые другие продвинутые возможности:

• Настройка вашей сборки через профили релиза (release profiles).
• Публикация библиотек на crates.io.
• Организация больших проектов с помощью рабочих пространств (workspaces).
• Установка бинарников из crates.io.
• Расширение Cargo с использованием пользовательских команд (custom commands).

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

[Настройка сборок с помощью профилей релиза]

В Rust профили релиза (release profiles) это предопределенные и настраиваемые профили с различными конфигурациями, позволяющими программисту получить больше контроля над различными опциями для компиляции кода. Каждый такой профиль конфигурируется независимо от других профилей.

В Cargo есть 2 основных профиля: профиль dev, который Cargo использует при запуске cargo build, и профиль release, используемый при запуске cargo build --release. Профиль dev определен с хорошими настройками по умолчанию для разработки, а профиль release содержит хорошие умолчания для сборок релиза.

Эти имена профилей могут быть вам знакомы из вывода сообщений сборки:

$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
$ cargo build --release
    Finished release [optimized] target(s) in 0.0s

Здесь dev и release это разные профили, используемые компилятором.

У Cargo есть настройки по умолчанию для каждого из профилей, которые применяются, когда вы не добавили явно какие-либо секции [profile.*] в файл Cargo.toml проекта. Путем добавления секций [profile.*] для любого профиля, который вы хотели бы настроить, вы переназначаете любое подмножество настроек по умолчанию. Например, так заданы значения по умолчанию опции оптимизации для профилей dev и release (файл Cargo.toml):

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

Настройка opt-level управляет количеством оптимизаций, которые Rust будет применять для вашего кода, она может быть указана в диапазоне от 0 до 3. Применение большего количества оптимизаций увеличивает время компиляции, так что если вы находитесь на стадии разработки и часто компилируете код, то вероятно заинтересованы в том, чтобы компиляция происходила быстрее, даже если результирующий код будет работать медленнее (конечно, отключение оптимизации дает также другие возможности, полезные для разработки). Таким образом, значение opt-level для профиля dev установлен в 0 (оптимизация отключена). Когда вы готовы выпустить свой код в релиз, лучшим решением будет разрешить оптимизацию, время компиляции при этом не столь критично, и код будет работать более эффективно. Поэтому настройка opt-level для профиля release установлена в 3.

Вы можете переназначить настройку по умолчанию путем добавления другого значения опции в файл Cargo.toml. Например, если мы хотим использовать уровень оптимизации 1 для профиля разработки, то можно добавить следующие 2 строки в файл Cargo.toml проекта:

[profile.dev]
opt-level = 1

Это переназначит значение 0 по умолчанию для opt-level. Теперь, когда вы запустите cargo build, Cargo будет использовать умолчания для профиля dev, плюс вашу настройку для опции opt-level. Поскольку здесь установлена opt-level в 1, Cargo будет применять больше оптимизаций, чем использовалось по умолчанию, но все же не так много, как это делается для сборки релиза.

Для получения полного списка опций конфигурации и умолчаний для каждого профиля см. документацию Cargo.

[Публикация крейта в crates.io]

Мы уже использовали пакеты из crates.io в качестве зависимостей (dependencies) нашего проекта, однако вы также можете расшарить свой код для других пользователей crates.io путем публикации ваших собственных пакетов. Реестр крейтов (crate registry) на домене crates.io распространяет исходный код ваших пакетов, поэтому на нем в первую очередь размещается open source код.

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

Создание полезных комментариев документации. Аккуратное документирование ваших пакетов поможет другим пользователям узнать, как и когда их можно использовать, так что стоит потратить некоторое время на написание документации. В главе 3 (см. [3]) мы обсуждали, как комментировать код Rust с помощью двух слешей //. Rust также имеет определенный вид комментария для документации, известный как documentation comment, который будет генерировать документацию HTML. HTML отобразит содержимое комментария документации для элементов public API, которые интересуют программистов. В них объясняется, как использовать ваш крейт (crate) в отличие от того, как этот крейт реализован.

Комментарий документации использует три слеша /// вместо двух, и поддерживает нотацию разметки (Markdown notation) для форматирования текста. Помещайте комментарий документации непосредственно перед элементом, который документируете. Листинг 14-1 показывает комментарии документации для функции add_one в крейте с именем my_crate (файл 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 }

Листинг 14-1. Пример documentation comment для функции.

Здесь мы предоставляем описание того, что делает функция add_one, начиная секцию с заголовком Examples, и затем показываем пример кода как использовать функцию add_one. Мы можем генерировать HTML-документацию из этого documentation comment путем запуска команды cargo doc. Эта команда запустит утилиту rustdoc, распространяемую вместе с Rust, и поместит сгенерированную HTML-документацию в директории target/doc.

Для удобства существует команда cargo doc --open, которая выполнит сборку HTML для документации вашего текущего крейта (как и документацию для всех зависимостей вашего крейта), и затем откроет результат в web браузере. Перейдите к функции add_one, и вы увидите, как будет сформирован текст в documentation comments:

Rust Cargo fig14 01

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

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

• Panics: сценарии, в которых документируемая функция может привести к панике. Код, вызывающий функцию, должен избегать таких ситуаций, если возникновение паники кода нежелательно.
• Errors: если функция возвращает Result, описывающий виды ошибок, которые могут произойти, и какие условия могут привести к таким видам ошибки. Это может быть полезно для анализа и обработки различными способами ситуаций ошибки в коде.
• Safety: если функция не безопасна для вызова (unsafety, это мы будем обсуждать в главе 19), то должна присутствовать секция документации с объяснением, почему функция небезопасна, а также с описанием инвариантов, которые функция ожидает от вызывающего кода.

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

Комментарии документации и тесты. Добавление блоков примера кода в ваших documentation comments может помочь в демонстрации, как использовать вашу библиотеку, и если делать так, то получается дополнительный бонус: запуск команды 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 test определит рассинхронизацию между примером и кодом!

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

Например, для добавления документации, описывающей назначение крейта my_crate, который содержит функцию add_one, мы добавляем комментарии документации, начинающиеся с //! в начало файла src/lib.rs, как показано в листинге 14-2 (файл 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.
// -- вырезано --

Листинг 14-2. Документация для крейта my_crate, описывающая крейт целиком.

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

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

Rust Cargo fig14 02

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

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

Экспорт удобного public API с помощью pub. Структура вашего публичного это основной фактор при публикации крейта. Программисты, кто будет использовать ваш крейт, менее знакомы со структурой интерфейса вашей библиотеки, чем вы, и у них могут возникнуть трудности с нахождением деталей, которые они захотят использовать, если ваш крейт имеет значительный объем иерархии модулей.

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

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

Например предположим, что мы сделали библиотеку с именем art для моделирования художественных концепций. В этой библиотеке есть два модуля: модуль kinds, содержащий два перечисления PrimaryColor и SecondaryColor, и модуль utils, содержащий функцию mix, как показано в листинге 14-3 (файл 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 { // -- вырезано -- } }

Листинг 14-3. Библиотека art в элементами, организованными в модулях kinds и utils.

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

Rust Cargo fig14 03

Рис. 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, который использует крейт art, должен был выяснить, что PrimaryColor находится в модуле kinds, а mix в модуле utils. Структура модуля крейта art более адекватна для разработчиков, работающих над библиотекой крейта art, а не для тех, кто её использует. Реальная внутренняя структура крейта не содержит никакой полезной информации для кого-нибудь, кто пытается понять, как использовать библиотеку крейта art, и скорее вызовет путаницу.

Чтобы устранить внутреннюю организацию из public API, мы можем модифицировать код крейта art в листинге 14-3, чтобы добавить операторы pub use для ре-экспорта элементов на верхний уровень, как показано в листинге 14-5 (файл 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 { // -- вырезано -- }

pub mod utils { // -- вырезано -- }

Листинг 14-5. Добавление операторов pub use для повторного экспорта элементов кода библиотеки крейта art.

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

Rust Cargo fig14 04

Рис. 14-4. Заглавная страница документации для art, которая перечисляет повторно экспортированные элементы.

Пользователи крейта art все еще будут видеть оригинальную внутреннюю структуру из листинга 14-3, как было показано в листинге 14-4, или они могут использовать более удобную структуру в листинге 14-5, как показано в листинге 14-6 (файл src/main.rs):

use art::mix;
use art::PrimaryColor;

fn main() { // -- вырезано -- }

Листинг 14-6. Программа использует ре-экспортированные элементы из крейта art.

В случаях, когда имеется множество вложенных модулей, реэкспорт типов на верхний уровень через pub use может существенно повлиять на опыт людей, использующих крейт. Другим распространенным использованием pub use является реэкспорт определений зависимостей в текущем крейте, чтобы сделать определения этого крейта частью public API вашего крейта.

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

Настройка учетной записи crates.io. Перед тем, как вы сможете публиковать любые крейты, необходимо создать учетную запись на crates.io и получить API token. Для этого посетите домашнюю страницу crates.io и залогиньтесь через учетную запись GitHub (учетная запись GitHub является обязательным требованием в настоящий момент, однако сайт в будущем может поддерживать другие способы создания учетной записи). Как только вы залогинились, посетите страницу настроек вашей учетной записи по адресу https://crates.io/me/, и получите ваш ключ (API key). Затем запустите команду cargo login и в ответ на запрос ключа вставьте свой API key, примерно так:

$ cargo login
m3MeOHodBPTyimenkyOu1r8509vSbuTM

Эта команда оповестит Cargo об вашем API token и сохранит его локально в файл ~/.cargo/credentials. Обратите внимание, что этот токен секретный: не передавайте его никому. Если вы по какой-то причине передадите кому-то этот токен, то вы должны отозвать его и сгенерировать новый токен на crates.io.

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

Вашему крейту понадобится уникальное имя. Пока вы работаете с крейтом локально, имя для него может быть произвольное. Однако имена крейтов на crates.io выделяются по принципу "кто первый, того и тапки". Как только было взято имя для крейта, никто больше не сможет опубликовать крейт с таким же именем. Прежде чем публиковать крейт, найдите для его подходящее имя, которое вы хотели бы использовать. Если имя уже используется, то вам нужно будет подобрать другое, и отредактировать соответствующим образом значение имени под секцией [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.
-- вырезано --
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

Эта ошибка произошла потому, что была упущена некоторая важная информация: требуется также описание и лицензия для того, чтобы люди знали, что делает ваш крейт, и на каких условиях они могут его использовать. В файле Cargo.toml добавьте описание (ключ description) из одного или двух предложений, потому что оно будет появляться вместе с вашим крейтом в результатах поиска. Для поля license вам нужно будет предоставить значение идентификатора лицензии. В таблице сайта Linux Foundation’s Software Package Data Exchange (SPDX) [5] перечислены идентификаторы, которые вы можете использовать для этого значения. Например, чтобы указать, что вы для своего крейта используете MIT License, добавьте идентификатор MIT (файл Cargo.toml):

[package]
name = "guessing_game"
license = "MIT"

Если вы по какой-то причине хотите использовать лицензию, которой нет в списке SPDX, то необходимо поместить текст лицензии в файл, включить этот файл в свой проект, и затем использовать license-file для указания этого файла вместо использования ключа license.

Руководство по выбору лицензии, подходящей для вашего проекта, выходит за рамки этой документации. Многие в сообществе Rust лицензируют свои проекты так же, как и Rust, используя двойную лицензию MIT OR Apache-2.0. Эта практика демонстрирует, что вы можете также указать несколько идентификаторов лицензий, отделенных друг от друга OR, чтобы применить эти несколько лицензий к своему проекту.

С добавленными в файл Cargo.toml версией (version), описанием (description), лицензией (license) а также уникальным именем (name) ваш проект готов к публикации, и секция [package] файла Cargo.toml может выглядеть следующим образом:

[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]

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

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

Будьте осторожны, потому что публикация является постоянной. Невозможно будет удалить код крейта, и даже ключ version не может быть перезаписан. Одна из основных целей 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 [unoptimized + debuginfo] target(s) in 0.19s
   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)

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

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

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

Yank версии не даст новым проектам использовать эту версию как зависимость, однако все существующие проекты, где эта зависимость была уже добавлена ранее, сохранят работоспособность. По сути yank означает, что все проекты с Cargo.lock не будут повреждены, и любые будущие сгенерированные файлы Cargo.lock не будут использовать yank-версию.

Чтобы применить yank для версии крейта, в директории ранее опубликованного крейта запустите команду 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

Устаревание yank не удаляет никакой код. Например, вы не сможете удалить случайно опубликованную секретную информацию. Если такое вдруг произошло, то необходимо немедленно сбросить состояние секретности для этой информации.

[Cargo Workspaces]

В главе 12 мы создали пакет, который подключал двоичный крейт (binary crate) и библиотечный крейт (library crate). По мере развития вашего проекта вы можете обнаружить, что библиотечный крейт разросся, и вы тогда захотите разделить свой пакет еще на несколько библиотечных крейтов. Cargo для этого предоставляет фичу, которая называется рабочие пространства (workspaces), что может помочь в управлении несколькими связанными пакетами, разрабатываемыми в тандеме.

Создание workspace. Рабочее пространство (workspace) это набор пакетов, которые совместно используют один и тот же файл Cargo.lock и директорию output. Давайте создадим проект, использующий workspace - будем использовать тривиальный код, чтобы было проще сконцентрироваться на структуре рабочего пространства. Существует несколько путей структурирования рабочего пространства, так что мы просто покажем самый распространенный способ. Пусть workspace будет содержать бинарник и де библиотеки. Бинарник, в котором будет предоставлен основной функционал, будет зависеть от двух библиотек. Одна библиотека будет предоставлять функцию add_one, а вторая функцию add_two. Эти три крейта будут частями одного и того же workspace. Начнем с создания новой директории для workspace:

$ mkdir add
$ cd add

Далее в директории add мы создаем файл Cargo.toml, который будет конфигурировать все workspace. В этом файле не будет секции [package]. Вместо этого он начнется с секции рабочего пространства [workspace], которая позволит нам добавлять члены в это рабочее пространство путем указания пути к пакету с нашим двоичным крейтом; в этом случае путь это adder (файл Cargo.toml):

[workspace]

members = [ "adder", ]

Далее мы создадим двоичный крейт adder, запустив команду cargo new в директории add:

$ cargo new adder
     Created binary (application) `adder` package

В этом текущем состоянии мы можем выполнить сборку для workspace запуском команды cargo build. Файлы в нашей директории add должны выглядеть примерно так:

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Рабочее пространство содержит одну директорию target на верхнем уровне, куда будут сохраняться компилируемые артефакты; у пакета adder не будет своей собственной директории target. Даже если мы запустим cargo build из директории adder, компилируемые артефакты все равно попадут в add/target вместо add/adder/target. Cargo структурирует директорию target в рабочем пространстве подобным образом, потому что крейты в рабочем пространстве должны зависеть друг от друга. Если бы у каждого крейта была собственная директория target, то каждый крейт должен был бы перекомпилироваться отдельно, чтобы артефакты компиляции складывались в свою директорию. Общая директория target в рабочем пространстве позволяет избежать ненужные перестройки.

Создание второго пакета в рабочем пространстве. Далее давайте добавим другой пакет как член рабочего пространства, и назовем его add_one. Поменяйте файл верхнего уровня Cargo.toml, чтобы указать путь add_one в списке members:

[workspace]

members = [ "adder", "add_one", ]

Затем сгенерируйте новый библиотечный крейт с именем add_one:

$ cargo new add_one --lib
     Created library `add_one` package

Ваша директория add и её файлы должны теперь выглядеть следующим образом:

├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

В фале add_one/src/lib.rs добавьте функцию add_one:

pub fn add_one(x: i32) -> i32 {
    x + 1
}

Теперь у нас есть пакет adder с бинарником, зависящем от пакета add_one, в котором содержится наша библиотека. Сначала нам нужно добавить зависимость path на add_one в файл adder/Cargo.toml:

[dependencies]
add_one = { path = "../add_one" }

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

Далее будем использовать функцию add_one (из крейта add_one) в крейте adder. Откройте файл adder/src/main.rs и добавьте в его начало строку use, чтобы привести в область действия библиотечный крейт add_one. Затем поменяйте функцию main для вызова функции add_one, как это сделано в листинге 14-7 (файл adder/src/main.rs):

use add_one;

fn main() { let num = 10; println!("Hello, world! {num} plus one is {}!", add_one::add_one(num)); }

Листинг 14-7. Использование библиотечного крейта add_one из крейта adder.

Теперь запустите команду cargo build из директории верхнего уровня add:

$ cargo build
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.68s

Чтобы запустить двоичный крейт из директории add, мы можем в команде cargo run с помощью аргумента -p указать, какой пакет в workspace мы хотим запустить:

$ cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!

Это запустит код в adder/src/main.rs, который зависит от крейта add_one.

Зависимость в рабочем пространстве от внешнего пакета. Обратите внимание, что в workspace есть только один файл Cargo.lock на верхнем уровне, вместо того чтобы Cargo.lock был в директории каждого крейта. Это гарантирует, что все крейты используют одну и ту же версию всех зависимостей. Если мы добавим в зависимости пакет rand к файлам adder/Cargo.toml и add_one/Cargo.toml, Cargo будет разрешать их оба для одной и той же версии rand, и запишет это в один файл Cargo.lock. Одинаковые версии зависимостей во всех крейтах рабочего пространства обеспечат их совместимость друг с другом. Добавьте крейт rand к секции [dependencies] в файле add_one/Cargo.toml, чтобы мы могли использовать крейт rand из крейта add_one:

[dependencies]
rand = "0.8.5"

Мы можем теперь добавить строку use rand; в файл add_one/src/lib.rs, и затем собрать все workspace целиком запуском cargo build в директории add, что приведет в область действия крейт rand и скомпилирует его. Мы получим предупреждение, потому что пока в коде нигде не было использование функций из крейта rand, который был приведен в область действия:

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   -- вырезано --
   Compiling rand v0.8.5
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
 --> add_one/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default
warning: `add_one` (lib) generated 1 warning Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 10.18s

Теперь файл верхнего уровня Cargo.lock содержит информацию о зависимости add_one от rand. Однако несмотря на то, что rand используется где-то в рабочей области, мы не можем использовать его в других крейтах рабочего пространства, если также не добавим rand в их файлы Cargo.toml. Например, если мы добавим use rand; в файл adder/src/main.rs для пакета adder, то получим ошибку:

$ cargo build
  -- вырезано --
   Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
 --> adder/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`

Чтобы это исправить, отредактируйте файл Cargo.toml пакета adder, и также покажите, что rand является зависимостью. Сборка пакета adder добавит rand в список зависимостей для adder в файл Cargo.lock, однако никакие дополнительные копии rand не будут загружены. Cargo гарантирует, что каждый крейт в каждом пакете рабочего пространства с использованием пакета rand будут применять одну и ту же версию, пока вы указываете совместимые версии rand, сохраняя пространство пакета и гарантируя, что крейты рабочего пространства совместимы друг с другом.

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

Добавление теста в workspace. Для другого улучшения давайте добавим тест функции add_one::add_one в крейт add_one (файл add_one/src/lib.rs):

pub fn add_one(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests { use super::*;
#[test] fn it_works() { assert_eq!(3, add_one(2)); } }

Теперь запустите команду cargo test в директории верхнего уровня add. Запуск cargo test в рабочем пространстве, структурированном подобным образом, запустит тесты для всех крейтов в рабочем пространстве:

$ cargo test
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.27s
     Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841)
running 1 test test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/adder-49979ff40686fa8e)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Первая секция вывод показывает, что тест it_works в крейте add_one прошел успешно. Следующая секция показывает, что в крейте adder найдено ноль тестов, и затем последняя секция показывает, что найдено ноль тестов документации в крейте add_one.

Мы также можем запустить тесты для каждого отдельного крейта в workspace из директории верхнего уровня, используя флаг -p с указанием крейта, который хотим тестировать:

$ cargo test -p add_one
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74)
running 1 test test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Этот вывод показывает, что cargo test запустил только тесты для крейта add_one, и тесты крейта adder не запускались.

Если вы публикуете крейты рабочего пространства на crates.io, то каждый крейт рабочего пространства нуждается в отдельной публикации. Наподобие cargo test мы можем публиковать отдельный крейт нашего рабочего пространства, используя флаг -p с указанием имени крейта, который хотим опубликовать.

Для дополнительной практики по аналогии добавьте в это рабочее пространство крейт add_two.

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

[Инсталляция бинарников с помощью cargo install]

Команда cargo install позволит вам установить и использовать бинарные крейты локально. Это не предназначено для замены системных пакетов; это удобный способ для разработчиков Rust установить инструменты, которые другие разработчики предоставили в общий доступ на crates.io. Обратите внимание, что вы можете установить только те пакеты, в которых есть двоичные цели компиляции (binary target). Binary target это запускаемая программа, которая создается, если в крейте есть файл src/main.rs или другой файл, указанный как binary, в отличие от library target, которая сама по себе не запускается, но подходит для использования в других программах. Обычно крейты содержат информацию в файле README, где описывается крейт: является ли он библиотекой, имеет ли binary target, либо содержит и то, и другое.

Все бинарники, установленные командой cargo install, сохраняются в корневой папке bin. Если вы установили Rust с помощью rustup.rs, и у вас нет никаких пользовательских конфигураций, то это директория будет находиться в $HOME/.cargo/bin. Обеспечьте, чтобы эта директория была в вашей переменной окружения $PATH, чтобы можно было запустить программы, установленные через cargo install.

Например, в главе 12 [7] упоминалось, что существует Rust-реализация утилиты grep с названием ripgrep для поиска файлов. Для инсталляции ripgrep мы можем запустить следующую команду:

$ cargo install ripgrep
    Updating crates.io index
  Downloaded ripgrep v13.0.0
  Downloaded 1 crate (243.3 KB) in 0.88s
  Installing ripgrep v13.0.0
-- вырезано --
   Compiling ripgrep v13.0.0
    Finished release [optimized + debuginfo] target(s) in 3m 10s
  Installing ~/.cargo/bin/rg
   Installed package `ripgrep v13.0.0` (executable `rg`)

Предпоследняя строка вывода показывает, место размещения и имя установленного бинарника, который в случае ripgrep будет rg. Пока директория инсталляции находится в вашей переменной окружения $PATH, как упоминалось ранее, вы можете запустить rg --help и начать использовать быструю версию утилиты поиска файлов, написанную на Rust.

[Расширение Cargo пользовательскими командами]

Утилита Cargo разработана таким образом, что вы можете расширять её возможности новыми субкомандами без модификации Cargo. Если бинарник, находящийся в вашей $PATH, называется cargo-something, то вы можете запустить его так, как если бы это была бы субкоманда Cargo, путем запуска cargo something. Пользовательские команды наподобие этой также перечисляются, когда вы запускаете команду cargo --list. Возможность использовать cargo install для установки расширений, и затем запускать их как встроенные инструменты Cargo - очень удобная фича.

[Общие выводы]

Обмен кодом с помощью Cargo и crates.io являются частью того, что делает экосистему Rust полезной для многих различных задач. Стандартная библиотека Rust маленькая и стабильная, однако крейтами можно легко делиться с другими пользователями и их использовать, улучшая тем самым временную шкалу крейтов отдельно от языка. Не стесняйтесь делиться полезным кодом на crates.io; вполне возможно, что он пригодится кому-нибудь еще!

[Ссылки]

1. Rust More About Cargo and Crates.io site:rust-lang.org.
2. The Cargo Book site:rust-lang.org.
3. Rust: общая концепция программирования.
4. Rust: управление проектами с помощью пакетов, крейтов и модулей.
5. SPDX License List site:spdx.org.
6. Semantic Versioning 2.0.0 site:semver.org.
7. Rust: пример консольной программы ввода/вывода.

 

Добавить комментарий


Защитный код
Обновить

Top of Page