Cargo.toml
: зависимости, метаданные
Управление версиями зависимостей
Профили сборки: release
, debug
Команды Cargo: build
, run
, test
Примеры: настройка сложного проекта
Управление итоговыми файлами и статическая линковка
Упражнение: Добавить зависимость и использовать её
Заключение
Сегодня мы погружаемся в один из самых важных инструментов экосистемы Rust — Cargo
. Это не просто утилита для сборки кода или управления зависимостями, а полноценная экосистема, которая объединяет в себе множество функций: от создания проектов до их тестирования, документирования и деплоя. Cargo — это ваш надёжный спутник на всех этапах разработки, будь вы новичком, только изучающим основы, или экспертом, создающим сложные приложения. В этой главе мы разберём его до мельчайших деталей, чтобы вы могли использовать его возможности на полную мощность.
Cargo — это больше, чем просто менеджер пакетов. Он автоматизирует рутинные задачи, такие как компиляция кода, загрузка зависимостей, запуск тестов и генерация документации. Он также предоставляет гибкие инструменты для настройки сборки, включая управление профилями и даже такие тонкости, как статическая линковка или изоляция итоговых файлов. Представьте Cargo как швейцарский нож для разработчика Rust: он делает всё, что нужно, и делает это хорошо. Наша цель — не просто познакомить вас с его базовыми функциями, а раскрыть все нюансы, чтобы вы могли уверенно решать любые задачи — от простых скриптов до многомодульных проектов.
Что мы будем изучать в этой главе? Мы начнём с детального разбора структуры конфигурационного файла Cargo.toml
, который лежит в основе каждого проекта Rust. Вы узнаете, как задавать метаданные, управлять зависимостями и настраивать фичи. Затем мы перейдём к управлению версиями зависимостей: как работает SemVer, зачем нужен Cargo.lock
и как обновлять библиотеки. Далее разберём профили сборки — debug
и release
, включая тонкости оптимизации и управления итоговыми файлами, чтобы ваши бинарники попадали в чистую папку, готовую для деплоя.
После этого мы углубимся в команды Cargo: от базовых cargo build
и cargo run
до мощных cargo test
и cargo doc
. Вы узнаете, как писать тесты всех типов — юнит-тесты, интеграционные и документационные — и как генерировать профессиональную документацию с примерами. Мы также рассмотрим практические примеры настройки сложных проектов с несколькими бинарниками и библиотеками. Особое внимание уделим вопросам, которые часто возникают у разработчиков: как изолировать итоговый бинарник в отдельной папке (например, с помощью --out-dir
) и как настроить статическую линковку для создания полностью независимых исполняемых файлов, например, с использованием таргета x86_64-unknown-linux-musl
.
В конце главы вас ждёт практическое упражнение: вы добавите зависимость в проект и используете её в коде, закрепив полученные знания. Эта лекция построена так, чтобы быть максимально самодостаточной: каждый раздел содержит подробные объяснения, примеры кода с комментариями и практические советы. Мы будем двигаться от простого к сложному, с избыточным покрытием всех аспектов, чтобы вы могли не только понять, как работает Cargo, но и применять его в реальных проектах с учётом всех нюансов. Независимо от вашего уровня подготовки, эта глава даст вам глубокое понимание инструмента, который станет вашим лучшим помощником в мире Rust. Приготовьтесь к погружению — и давайте начнём!
Cargo.toml
: зависимости, метаданные
Cargo.toml
— это краеугольный камень любого проекта Rust. Этот файл, написанный в формате TOML (Tom’s Obvious, Minimal Language), определяет всё: от имени и версии проекта до списка зависимостей и настроек сборки. TOML — это простой, но строгий язык конфигурации, который сочетает читаемость с точностью. В этом разделе мы разберём каждую секцию Cargo.toml
с примерами, объяснениями и практическими советами. Вы узнаете, как правильно настроить проект, чтобы он был не только функциональным, но и готовым к публикации или совместной разработке. Погрузимся в детали!
[package]
Секция [package]
— это метаданные вашего проекта. Она обязательна, так как Cargo использует её для идентификации проекта, генерации бинарников и публикации на crates.io. Здесь вы задаёте основные характеристики, которые делают ваш проект уникальным и понятным для других разработчиков.
Основные поля:
name
: Уникальное имя проекта. Должно быть валидным идентификатором Rust (буквы, цифры, _
, -
, без пробелов). Определяет имя бинарника или crate.version
: Версия в формате SemVer (MAJOR.MINOR.PATCH
, например, 0.1.0
). Указывает текущую стадию разработки.edition
: Редакция Rust (2015
, 2018
, 2021
). Определяет синтаксис и возможности языка. Рекомендуется 2021
для новых проектов.Дополнительные поля:
authors
: Список авторов (например, ["Иван Иванов <ivan@example.com>"]
). Опционально, но полезно для документации.description
: Краткое описание проекта. Важно для crates.io.license
: Лицензия (например, MIT
, Apache-2.0
). Обязательно для публикации.homepage
: URL домашней страницы.repository
: URL репозитория (например, GitHub).readme
: Путь к файлу README (например, README.md
).keywords
: Список до 5 ключевых слов для поиска на crates.io.categories
: Категории crates.io (например, ["utilities"]
).
Пример минимального Cargo.toml
:
[package]
name = "simple_app"
version = "0.1.0"
edition = "2021"
Этот пример создаёт базовый проект с именем simple_app
, версией 0.1.0
и редакцией 2021
. Cargo сгенерирует бинарник с таким же именем при сборке.
Пример полного Cargo.toml
:
[package]
name = "complex_app"
version = "0.2.3"
edition = "2021"
authors = ["Алексей Петров <alex@example.com>", "Мария Иванова <maria@example.com>"]
description = "Многофункциональное приложение на Rust"
license = "MIT OR Apache-2.0"
homepage = "https://example.com/complex_app"
repository = "https://github.com/user/complex_app"
readme = "README.md"
keywords = ["rust", "cargo", "utility"]
categories = ["command-line-utilities", "development-tools"]
Здесь мы добавили все возможные метаданные. Такой файл готов к публикации на crates.io: он информативен, содержит лицензию и ссылки на ресурсы. Поле license = "MIT OR Apache-2.0"
указывает двойное лицензирование, что популярно в Rust-сообществе.
Комментарии и практические советы:
name
на crates.io перед публикацией.edition = "2021"
, чтобы получить доступ к новым возможностям, например, улучшенным замыканиям.readme
и repository
для удобства пользователей.name = "my_app" # Имя проекта, используется как имя бинарника
version = "0.1.0" # Начальная версия
[dependencies]
Секция [dependencies]
перечисляет внешние библиотеки (crates), необходимые для работы вашего проекта. Это ядро функциональности, которое вы добавляете к своему коду.
Формат записи:
имя = "версия"
.имя = { version = "версия", features = [...], optional = true/false, path = "путь", git = "URL" }
.Пример с пояснениями:
[dependencies]
serde = "1.0.152" # Точная версия для сериализации
rand = { version = "0.8.5", features = ["small_rng"] } # С фичами для компактного RNG
tokio = { version = "1.20", optional = true } # Опциональная асинхронная библиотека
my_local_crate = { path = "../my_local_crate" } # Локальная зависимость
experimental = { git = "https://github.com/user/experimental", branch = "dev" } # Из git
Объяснение параметров:
serde = "1.0.152"
: Указывает точную версию библиотеки для сериализации данных. Cargo загрузит именно её.rand = { version = "0.8.5", features = ["small_rng"] }
: Версия 0.8.5
с включённой фичей small_rng
, которая добавляет компактный генератор случайных чисел.tokio = { version = "1.20", optional = true }
: Зависимость включается только при активации соответствующей фичи через [features]
.path = "../my_local_crate"
: Указывает на локальный crate в соседней директории. Полезно для разработки.git = "https://github.com/user/experimental", branch = "dev"
: Загружает crate из git-репозитория, используя ветку dev
. Можно указать tag
или rev
вместо branch
.
Cargo автоматически загружает зависимости из реестра crates.io, если не указан path
или git
. Версии обрабатываются с учётом SemVer, о чём мы поговорим в следующем разделе.
[dev-dependencies]
Секция [dev-dependencies]
предназначена для зависимостей, используемых только во время разработки — для тестов, бенчмарков или утилит. Они не включаются в финальный бинарник при сборке с --release
.
Пример и назначение:
[dev-dependencies]
criterion = "0.4.0" # Для бенчмарков производительности
pretty_assertions = "1.3" # Улучшенные сообщения об ошибках в тестах
Здесь criterion
используется для измерения производительности кода, а pretty_assertions
улучшает вывод ошибок в тестах, показывая различия между ожидаемым и фактическим результатом.
[build-dependencies]
Секция [build-dependencies]
перечисляет зависимости, необходимые для скрипта сборки build.rs
. Этот скрипт выполняется перед основной компиляцией и может генерировать код или выполнять другие задачи.
Пример и назначение:
[build-dependencies]
cc = "1.0.79" # Компиляция C-кода
bindgen = "0.65" # Генерация биндингов для FFI
cc
позволяет компилировать C-код, а bindgen
генерирует Rust-биндинги для C-библиотек. Используется, например, для интеграции с внешними системами через FFI.
[features]
Секция [features]
определяет пользовательские фичи для условной компиляции. Это позволяет включать или отключать части кода и зависимости в зависимости от нужд проекта.
Пример и использование:
[features]
default = ["basic"] # Фичи по умолчанию
basic = [] # Пустая фича (флаг)
async = ["tokio"] # Включает tokio
[dependencies]
tokio = { version = "1.20", optional = true }
Здесь default
активирует фичу basic
при сборке без указания фич. Фича async
включает tokio
. Запуск с фичей:
cargo build --features async
edition
, чтобы избежать проблем совместимости.serde = "1.0" # Сериализация и десериализация данных
optional
и [features]
для гибкости.cargo check
.path
.
Управление версиями зависимостей — это одна из ключевых задач Cargo, которая обеспечивает стабильность и совместимость вашего проекта. В Rust используется стандарт Semantic Versioning (SemVer), который позволяет разработчикам точно указывать, какие версии библиотек нужны, и как они могут обновляться. В этом разделе мы разберём, как работает SemVer, как задавать версии в Cargo.toml
, зачем нужен Cargo.lock
, как обновлять зависимости и какие инструменты помогут анализировать их состояние. Мы углубимся в детали, чтобы вы могли уверенно управлять зависимостями в проектах любого масштаба.
MAJOR.MINOR.PATCH
)
SemVer — это стандарт версионирования, принятый в Rust и многих других экосистемах. Он состоит из трёх чисел, разделённых точками: MAJOR.MINOR.PATCH
. Например, версия 1.2.3
означает:
MAJOR
(1): Основная версия. Увеличивается при несовместимых изменениях в API, которые требуют адаптации кода (например, удаление функции).MINOR
(2): Малая версия. Увеличивается при добавлении новой функциональности, совместимой с предыдущими версиями (например, новая функция).PATCH
(3): Исправление. Увеличивается при исправлении ошибок без изменения API.
SemVer позволяет Cargo автоматически определять, какие обновления безопасны. Например, если вы указали serde = "1.0.0"
, то обновление до 1.0.1
(патч) или 1.1.0
(минор) считается совместимым, а до 2.0.0
— нет, так как это может сломать ваш код. Понимание SemVer — это основа для работы с зависимостями в Rust.
"1.0.0"
, "^1.0.0"
, "~1.0.0"
, диапазоны)
В Cargo.toml
вы указываете версии зависимостей с помощью спецификаторов. Cargo поддерживает несколько форматов, которые дают вам гибкость в выборе подходящих версий.
"1.0.0"
: Точная версия. Cargo загрузит именно 1.0.0
, без обновлений."^1.0.0"
: Совместимые обновления. Означает >=1.0.0, <2.0.0
. Это поведение по умолчанию в Cargo. Например, 1.0.1
или 1.1.0
подойдут, но 2.0.0
— нет."~1.0.0"
: Только патчи. Означает >=1.0.0, <1.1.0
. Подходят 1.0.1
, 1.0.2
, но не 1.1.0
.">=1.0, <2.0"
: Диапазон версий. Указывает явные границы (например, от 1.0.0
до 1.999.999
)."1.*"
: Любая версия в пределах 1.x.x
. Не рекомендуется из-за риска несовместимости.
Каждый спецификатор подходит для разных случаев. Например, "^1.0.0"
хорош для большинства зависимостей, так как позволяет получать новые функции и исправления, сохраняя совместимость. "~1.0.0"
полезен, если вы хотите только исправления ошибок, а "1.0.0"
— если нужна строгая фиксация.
Cargo.toml
Вот как это выглядит в реальном Cargo.toml
:
[dependencies]
serde = "^1.0" # Любая совместимая версия от 1.0.0 до <2.0.0
rand = "~0.8.5" # Только патчи: от 0.8.5 до <0.9.0
tokio = ">=1.15, <1.25" # Диапазон версий
log = "0.4.17" # Точная версия
В этом примере serde
может обновляться до 1.1.0
, но не до 2.0.0
. rand
ограничен патчами (0.8.6
подойдёт, 0.9.0
— нет). tokio
находится в диапазоне, а log
зафиксирован на 0.4.17
.
Cargo.lock
: назначение, пример, правила использования
Cargo.lock
— это файл, который фиксирует точные версии всех зависимостей (включая транзитивные) после первой сборки проекта. Он генерируется автоматически при выполнении cargo build
или cargo update
и обеспечивает воспроизводимость сборки.
Назначение:
Пример Cargo.lock
:
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc 0.2.139",
"rand_core 0.6.4",
]
Здесь зафиксирована версия rand
(0.8.5
) с её зависимостями и контрольной суммой для проверки целостности.
Правила использования:
Cargo.lock
в git. Это обеспечивает одинаковую сборку на всех машинах.Cargo.lock
из git (добавьте в .gitignore
). Это позволяет пользователям вашей библиотеки выбирать совместимые версии зависимостей.Чтобы проверить содержимое:
cat Cargo.lock | grep rand
cargo update
, -p
)
Cargo позволяет обновлять зависимости в пределах указанных в Cargo.toml
ограничений.
cargo update
: Обновляет все зависимости до последних совместимых версий, переписывая Cargo.lock
.cargo update -p имя
: Обновляет только указанный crate.Пример:
$ cat Cargo.lock | grep rand
version = "0.8.4"
$ cargo update
$ cat Cargo.lock | grep rand
version = "0.8.5"
Здесь rand
обновился с 0.8.4
до 0.8.5
, так как это совместимо с "~0.8.5"
. Для конкретного crate:
cargo update -p rand
cargo tree
, cargo outdated
)Cargo предоставляет дополнительные утилиты для анализа зависимостей.
cargo tree
: Показывает дерево зависимостей.cargo install cargo-tree
.cargo tree -p serde
Выводит зависимости serde
, включая транзитивные.
cargo outdated
: Показывает устаревшие зависимости.cargo install cargo-outdated
.cargo outdated
Выводит список crates с доступными новыми версиями.
Эти инструменты помогают выявлять конфликты версий и поддерживать проект в актуальном состоянии.
cargo update
, но проверяйте изменения в Cargo.lock
перед коммитом."^1.0"
для большинства зависимостей, чтобы получать обновления."1.0.0"
) для критически важных библиотек."*"
— это может привести к несовместимости.cargo tree
перед релизом, чтобы избежать дубликатов.cargo outdated
для планирования обновлений.release
, debug
Cargo поддерживает профили сборки, которые определяют, как компилятор rustc
обрабатывает ваш код. Профили — это способ настроить баланс между скоростью компиляции, производительностью бинарника и удобством отладки. По умолчанию Cargo предлагает два основных профиля: debug
для разработки и release
для продакшена. В этом разделе мы разберём их особенности, настройки и управление итоговыми файлами, чтобы вы могли собирать бинарники так, как вам нужно — вплоть до изоляции их в чистую папку для деплоя. Погрузимся в детали!
debug
: особенности, пример
Профиль debug
используется по умолчанию, когда вы запускаете cargo build
или cargo run
без дополнительных флагов. Его главная цель — обеспечить быструю компиляцию и удобство отладки, жертвуя производительностью.
Особенности:
opt-level = 0
: Минимальная оптимизация. Код компилируется быстро, но работает медленно.debug = true
: Включает отладочную информацию (например, символы для отладчиков вроде gdb
).debug-assertions = true
: Включает проверки assert!
в коде.target/debug/
.Пример:
$ cargo build
Compiling my_app v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
$ ls target/debug/
my_app my_app.d
Здесь my_app
— это скомпилированный бинарник, а my_app.d
— файл с метаданными для отладки. Размер бинарника будет больше из-за отсутствия оптимизаций:
$ du -h target/debug/my_app
4.2M target/debug/my_app
Профиль debug
идеален для разработки: вы быстро проверяете изменения и можете использовать отладчик для поиска ошибок.
release
: особенности, пример
Профиль release
активируется с флагом --release
(cargo build --release
). Его цель — создать максимально оптимизированный бинарник для продакшена, жертвуя скоростью компиляции.
Особенности:
opt-level = 3
: Максимальная оптимизация. Код работает быстро, но компиляция занимает больше времени.debug = false
: Отладочная информация отключена по умолчанию.debug-assertions = false
: Проверки assert!
удаляются.target/release/
.Пример:
$ cargo build --release
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
$ ls target/release/
my_app my_app.d deps/ incremental/
Бинарник my_app
меньше и быстрее, чем в debug
:
$ du -h target/release/my_app
1.8M target/release/my_app
Однако в target/release/
появляются дополнительные файлы (deps/
, incremental/
), что может быть неудобно для деплоя. Мы разберём это ниже в разделе управления итоговыми файлами.
Cargo.toml
(opt-level
, lto
, codegen-units
, strip
)
Вы можете переопределить настройки профилей в Cargo.toml
, чтобы адаптировать их под свои нужды. Это делается в секциях [profile.dev]
и [profile.release]
.
Пример:
[profile.dev]
opt-level = 1 # Лёгкая оптимизация даже в debug
debug-assertions = true # Включить проверки
[profile.release]
opt-level = 3 # Максимальная оптимизация
lto = "thin" # Thin Link-Time Optimization
codegen-units = 1 # Минимум параллелизма для лучшей оптимизации
strip = "symbols" # Удалить символы
Объяснение параметров:
opt-level
: Уровень оптимизации.
0
: Без оптимизаций (быстрая компиляция).1
: Базовая оптимизация.2
: Умеренная оптимизация.3
: Максимальная оптимизация."s"
: Оптимизация для размера."z"
: Минимальный размер.lto
: Link-Time Optimization (оптимизация на этапе линковки).
false
: Отключено."thin"
: Быстрая, но менее агрессивная оптимизация."fat"
: Максимальная оптимизация, но медленная.codegen-units
: Количество единиц компиляции. Меньше значение — лучше оптимизация, но дольше сборка. По умолчанию 16.strip
: Удаление данных из бинарника.
"none"
: Ничего не убирать."debuginfo"
: Убрать отладочную информацию."symbols"
: Убрать все символы.
С этими настройками release
создаёт компактный и быстрый бинарник, а debug
становится чуть быстрее благодаря opt-level = 1
.
Одной из частых задач при сборке является управление итоговыми файлами. По умолчанию Cargo помещает бинарники в target/debug/
или target/release/
, но эти папки содержат не только исполняемый файл, но и промежуточные артефакты, что неудобно для деплоя. Давайте разберём, как это исправить.
target/release/
После cargo build --release
в target/release/
оказываются:
my_app
: сам бинарник.my_app.d
: файл зависимостей для отладки.deps/
: папка с промежуточными файлами зависимостей.incremental/
: кеш инкрементальной компиляции.
Если вы хотите скопировать бинарник в другое место (например, для деплоя), приходится вручную выбирать только my_app
, что неудобно.
--out-dir
Начиная с Rust 1.58, Cargo поддерживает флаг --out-dir
, который копирует только финальный бинарник в указанную папку, оставляя промежуточные файлы в target/
.
Пример:
$ cargo build --release --out-dir dist
$ ls dist/
my_app
$ ls target/release/
my_app my_app.d deps/ incremental/
Теперь dist/
содержит только my_app
, готовый для копирования куда угодно. Папку dist
нужно создать заранее, так как Cargo этого не делает.
build.rs
Если вы хотите автоматизировать процесс, можно использовать скрипт сборки build.rs
, который выполняется перед компиляцией.
Пример build.rs
:
use std::fs;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
if std::env::var("PROFILE").unwrap() == "release" {
let binary_name = "my_app"; // Замените на имя вашего проекта
let source = format!("target/release/{}", binary_name);
let dest_dir = "dist";
fs::create_dir_all(dest_dir).unwrap();
fs::copy(&source, format!("{}/{}", dest_dir, binary_name)).unwrap();
}
}
Использование:
$ cargo build --release
$ ls dist/
my_app
Скрипт проверяет, что сборка происходит в профиле release
, создаёт папку dist
и копирует туда бинарник. Добавьте build.rs
в корень проекта и укажите имя бинарника, соответствующее name
в Cargo.toml
.
debug
для разработки, release
для продакшена.opt-level = 1
в [profile.dev]
, если проект большой и тормозит в debug
.lto = "thin"
для баланса скорости и размера в release
.--out-dir dist
для финальных сборок, чтобы упростить деплой.build.rs
, если вам нужен единый процесс для команды.du -h
после изменений настроек.build
, run
, test
Cargo предоставляет набор команд, которые делают разработку на Rust удобной и эффективной. Эти команды покрывают всё: от компиляции кода до запуска тестов и генерации документации. В этом разделе мы разберём основные команды — cargo build
, cargo run
, cargo test
и cargo doc
— с примерами, флагами и нюансами. Мы также коснёмся дополнительных команд и дадим практические советы, чтобы вы могли использовать Cargo на полную мощность. Погружаемся в детали!
cargo build
Описание: Команда cargo build
компилирует проект, создавая исполняемые файлы или библиотеки. Это основа работы с Cargo, которая переводит ваш код из исходников в бинарники.
Флаги:
--release
: Использует профиль release
для оптимизированной сборки.--out-dir путь
: Копирует финальный бинарник в указанную папку (доступно с Rust 1.58).--target имя
: Указывает целевой таргет для кросс-компиляции (например, x86_64-unknown-linux-musl
).--verbose
: Показывает подробный лог компиляции.
По умолчанию сборка происходит в профиле debug
, а бинарник попадает в target/debug/
. С --release
— в target/release/
.
Пример:
$ cargo build
Compiling my_app v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
$ ls target/debug/
my_app my_app.d
$ cargo build --release --out-dir dist
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
$ ls dist/
my_app
В первом случае мы собрали проект в debug
, во втором — в release
с копированием бинарника в dist/
. Флаг --out-dir
упрощает деплой, изолируя итоговый файл.
cargo run
Описание: Команда cargo run
компилирует проект и сразу запускает основной бинарник. Это удобный способ проверить код в действии без лишних шагов.
Флаги: Аналогичны cargo build
:
--release
: Запуск в профиле release
.--out-dir путь
: Копирует бинарник в указанную папку перед запуском.--verbose
: Подробный вывод.
Пример: Для проекта с src/main.rs
:
fn main() {
println!("Привет, мир!");
}
$ cargo run
Compiling my_app v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/my_app`
Привет, мир!
$ cargo run --release
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
Running `target/release/my_app`
Привет, мир!
Команда сначала компилирует, а затем выполняет my_app
. В release
запуск быстрее благодаря оптимизациям.
cargo test
Описание: Команда cargo test
запускает все тесты в проекте: юнит-тесты, интеграционные и документационные. Это мощный инструмент для проверки корректности кода, который компилирует тесты в специальном режиме и выполняет их параллельно.
Типы тестов:
#[cfg(test)]
внутри файла (обычно src/lib.rs
или src/main.rs
). Тестируют внутреннюю логику.tests/
. Проверяют публичный API.```rust
и ```
), который автоматически тестируется.Примеры кода для каждого типа:
Юнит-тесты в src/lib.rs
:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, -2), -3);
}
#[test]
#[should_panic(expected = "overflow")]
fn test_overflow() {
add(i32::MAX, 1);
}
#[test]
#[ignore = "too slow"]
fn test_slow() {
std::thread::sleep(std::time::Duration::from_secs(2));
assert!(true);
}
}
Интеграционные тесты в tests/integration.rs
:
use my_app::add;
#[test]
fn test_integration() {
assert_eq!(add(5, 7), 12);
}
Документационные тесты в src/lib.rs
:
/// Adds two numbers.
///
/// ```rust
/// use my_app::add;
/// assert_eq!(add(2, 2), 4);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Флаги:
--nocapture
: Показывает вывод println!
из тестов.--ignored
: Запускает тесты с #[ignore]
.--jobs N
: Устанавливает количество параллельных тестов.--test имя
: Запускает тесты из конкретного файла.Пример запуска:
$ cargo test
running 4 tests
test tests::test_add_positive ... ok
test tests::test_add_negative ... ok
test tests::test_overflow ... ok
test tests::test_slow ... ignored
running 1 test
test test_integration ... ok
Doc-tests my_app
running 1 test
test src/lib.rs - add (line 5) ... ok
test result: ok. 5 passed; 0 failed; 1 ignored
$ cargo test -- --nocapture
Здесь тесты проходят, а test_slow
пропущен из-за #[ignore]
. С --nocapture
вы увидите вывод, если добавите println!
.
Использование зависимостей (например, pretty_assertions
):
Добавим в Cargo.toml
:
[dev-dependencies]
pretty_assertions = "1.3"
Изменим тест:
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_add_pretty() {
assert_eq!(add(2, 2), 5); // Показывает diff при ошибке
}
}
Если тест провалится, pretty_assertions
выведет подробное сравнение ожидаемого и фактического результата.
cargo doc
Описание: Команда cargo doc
генерирует HTML-документацию на основе комментариев в коде. Она сканирует ///
(внешние комментарии) и //!
(внутренние комментарии), создавая файлы в target/doc/
.
Пример документированного кода: В src/lib.rs
:
//! Библиотека для математических операций
//!
//! # Возможности
//! - Сложение чисел
/// Добавляет два числа и возвращает результат.
///
/// # Аргументы
/// * `a` - Первое число
/// * `b` - Второе число
///
/// # Примеры
/// ```rust
/// use my_app::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Генерация документации:
$ cargo doc
Documenting my_app v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.45s
$ ls target/doc/
my_app ...
Открыть в браузере:
$ cargo doc --open
Флаги:
--no-deps
: Генерирует документацию только для вашего проекта, игнорируя зависимости.--open
: Открывает документацию в браузере.--document-private-items
: Включает приватные элементы в документацию.Пример с приватными элементами:
$ cargo doc --document-private-items
pub
будут задокументированы.pub
(доступные только внутри модуля или крейта) также попадут в документацию.Допустим, у вас есть такой код в lib.rs
:
/// Это публичная функция
pub fn public_function() {
println!("Я публичный!");
}
/// Это приватная функция
fn private_function() {
println!("Я приватный!");
}
--document-private-items
: После выполнения cargo doc
в документации будет только public_function
.--document-private-items
: После выполнения cargo doc --document-private-items
в документации появятся оба метода: public_function
и private_function
.Вы можете комбинировать --document-private-items
с другими опциями cargo doc
:
--open
: Открывает сгенерированную документацию в браузере сразу после создания:
cargo doc --document-private-items --open
--no-deps
: Исключает документацию зависимостей, фокусируясь только на вашем коде:
cargo doc --document-private-items --no-deps
cargo check
: Проверяет код на ошибки без генерации бинарника. Быстрее, чем cargo build
.
$ cargo check
cargo clean
: Удаляет папку target/
, очищая все артефакты.
$ cargo clean
cargo check
для быстрой проверки синтаксиса.--release
для финальных тестов производительности.cargo test
.--nocapture
для отладки тестов с выводом.--no-deps
для ускорения в больших проектах.--out-dir
в скриптах деплоя.
До сих пор мы разбирали Cargo на уровне отдельных концепций: структуру Cargo.toml
, профили сборки, команды и зависимости. Теперь пришло время собрать всё вместе и показать, как настроить сложный проект с несколькими бинарниками и библиотекой. В этом разделе мы создадим пример проекта — мини-приложение для обработки данных, которое будет включать библиотеку с общей логикой и два бинарника: сервер и клиент. Вы увидите, как организовать Cargo.toml
, структуру проекта, код и как собрать и запустить всё это. Этот пример станет мостом между теорией и практикой, демонстрируя возможности Cargo в реальном сценарии. Погружаемся!
Cargo.toml
с несколькими бинарниками
Для сложного проекта с несколькими исполняемыми файлами мы используем секцию [[bin]]
в Cargo.toml
. Это позволяет указать несколько бинарников, каждый со своим именем и исходным файлом. Также добавим библиотеку и зависимости.
Пример Cargo.toml
:
[package]
name = "data_processor"
version = "0.1.0"
edition = "2021"
description = "Приложение для обработки данных с сервером и клиентом"
authors = ["Ваше Имя "]
license = "MIT"
[lib]
name = "processor_lib"
path = "src/lib.rs"
[[bin]]
name = "server"
path = "src/bin/server.rs"
[[bin]]
name = "client"
path = "src/bin/client.rs"
[dependencies]
serde = { version = "1.0", features = ["derive"] } # Сериализация данных
tokio = { version = "1.20", features = ["full"], optional = true } # Асинхронность
[features]
default = ["async"]
async = ["tokio"]
Объяснение:
[package]
: Метаданные проекта. Имя data_processor
— это общий идентификатор, но бинарники будут иметь свои имена.[lib]
: Определяет библиотеку с именем processor_lib
, исходники которой находятся в src/lib.rs
.[[bin]]
: Две секции для бинарников server
и client
. Каждый указывает путь к своему файлу в src/bin/
.[dependencies]
: serde
для сериализации, tokio
как опциональная зависимость для асинхронности.[features]
: Фича async
включает tokio
, а default
активирует её по умолчанию.
Этот Cargo.toml
задаёт проект с общей библиотекой и двумя бинарниками, которые могут использовать её функциональность.
Структура проекта должна соответствовать настройкам в Cargo.toml
. Вот как будет выглядеть дерево файлов:
data_processor/
├── Cargo.toml
├── src/
│ ├── lib.rs # Общая библиотека
│ ├── bin/
│ │ ├── server.rs # Бинарник сервера
│ │ └── client.rs # Бинарник клиента
Объяснение:
Cargo.toml
: В корне проекта, как всегда.src/lib.rs
: Файл библиотеки, содержащий общую логику.src/bin/
: Папка для бинарников. Каждый файл здесь становится отдельным исполняемым файлом, если указан в [[bin]]
.
Такая структура типична для проектов с несколькими бинарниками в Rust. Библиотека в lib.rs
позволяет вынести общий код, избегая дублирования.
lib.rs
и бинарниковТеперь добавим код, чтобы показать, как всё работает вместе.
src/lib.rs
:
//! Библиотека для обработки данных
use serde::{Serialize, Deserialize};
/// Структура для представления данных
#[derive(Serialize, Deserialize, Debug)]
pub struct Data {
id: u32,
value: String,
}
/// Создаёт новый объект данных
pub fn create_data(id: u32, value: &str) -> Data {
Data {
id,
value: value.to_string(),
}
}
/// Сериализует данные в JSON
pub fn to_json(data: &Data) -> String {
serde_json::to_string(data).unwrap_or_else(|e| format!("Ошибка: {}", e))
}
Эта библиотека определяет структуру Data
и функции для работы с ней. Она использует serde
для сериализации в JSON.
src/bin/server.rs
:
#[tokio::main]
async fn main() {
let data = processor_lib::create_data(1, "Серверные данные");
let json = processor_lib::to_json(&data);
println!("Сервер отправляет: {}", json);
}
Сервер использует tokio
для асинхронного выполнения и библиотеку processor_lib
для создания и сериализации данных.
src/bin/client.rs
:
fn main() {
let data = processor_lib::create_data(2, "Клиентские данные");
let json = processor_lib::to_json(&data);
println!("Клиент получил: {}", json);
}
Клиент работает синхронно, демонстрируя, что библиотека универсальна для разных подходов.
Теперь соберём и запустим проект, чтобы увидеть всё в действии.
Сборка:
$ cargo build
Compiling data_processor v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 1.23s
$ ls target/debug/
client client.d server server.d ...
В target/debug/
появляются два бинарника: client
и server
. Библиотека компилируется автоматически как зависимость.
Запуск:
$ cargo run --bin server
Compiling data_processor v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 1.23s
Running `target/debug/server`
Сервер отправляет: {"id":1,"value":"Серверные данные"}
$ cargo run --bin client
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/client`
Клиент получил: {"id":2,"value":"Клиентские данные"}
Флаг --bin имя
указывает, какой бинарник запустить. Второй запуск быстрее благодаря инкрементальной компиляции.
Сборка для релиза с изоляцией:
$ cargo build --release --out-dir dist
Compiling data_processor v0.1.0
Finished release [optimized] target(s) in 2.34s
$ ls dist/
client server
С --out-dir dist
оба бинарника попадают в чистую папку dist/
, готовые для деплоя.
Запуск из dist/
:
$ ./dist/server
Сервер отправляет: {"id":1,"value":"Серверные данные"}
$ ./dist/client
Клиент получил: {"id":2,"value":"Клиентские данные"}
Теперь бинарники можно копировать куда угодно без лишних файлов.
После сборки проекта в Rust итоговые файлы — бинарники — часто нужно подготовить для деплоя или запуска на других системах. Однако стандартный процесс оставляет их в папке target/
вместе с промежуточными артефактами, а динамическая линковка может создавать зависимости от системных библиотек. В этом разделе мы разберём два ключевых аспекта: как изолировать итоговые бинарники в чистую папку и как настроить статическую линковку для создания полностью независимых исполняемых файлов. Мы рассмотрим проблемы, решения, примеры и автоматизацию, чтобы вы могли гибко управлять результатами сборки. Погружаемся в детали!
По умолчанию Cargo помещает бинарники в target/debug/
или target/release/
, но эти папки содержат не только исполняемые файлы, что усложняет их перенос. Давайте разберём, как это исправить.
target/release/
После выполнения cargo build --release
в target/release/
вы увидите:
my_app
: Основной бинарник.my_app.d
: Файл зависимостей для отладки.deps/
: Папка с промежуточными файлами зависимостей.incremental/
: Кеш инкрементальной компиляции.Пример:
$ cargo build --release
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
$ ls target/release/
my_app my_app.d deps/ incremental/
Если вы хотите скопировать бинарник для деплоя, вам нужно вручную извлечь только my_app
, игнорируя остальные файлы. Это неудобно, особенно при автоматизации.
--out-dir
Cargo (с Rust 1.58) предлагает флаг --out-dir
, который копирует только финальные бинарники в указанную папку, оставляя промежуточные файлы в target/
. Это идеальное решение для изоляции результатов сборки.
Пример:
$ mkdir dist # Создаём папку заранее
$ cargo build --release --out-dir dist
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
$ ls dist/
my_app
$ ls target/release/
my_app my_app.d deps/ incremental/
Теперь dist/
содержит только my_app
, готовый для копирования. Обратите внимание: Cargo не создаёт dist/
автоматически, так что используйте mkdir
или скрипт.
Для нескольких бинарников (как в предыдущем разделе):
$ cargo build --release --out-dir dist
$ ls dist/
client server
build.rs
Если вы хотите встроить изоляцию в процесс сборки, используйте скрипт build.rs
. Он выполняется перед компиляцией и может копировать бинарники автоматически.
Пример build.rs
:
use std::fs;
fn main() {
println!("cargo:rerun-if-changed=build.rs"); // Перезапуск при изменении скрипта
if std::env::var("PROFILE").unwrap() == "release" {
let binary_name = "my_app"; // Замените на имя вашего проекта
let source = format!("target/release/{}", binary_name);
let dest_dir = "dist";
fs::create_dir_all(dest_dir).unwrap();
fs::copy(&source, format!("{}/{}", dest_dir, binary_name)).unwrap();
}
}
Поместите этот файл в корень проекта. Теперь при сборке:
$ cargo build --release
Compiling my_app v0.1.0
Finished release [optimized] target(s) in 1.45s
$ ls dist/
my_app
Скрипт проверяет профиль (release
), создаёт dist/
и копирует туда бинарник. Для нескольких бинарников добавьте их имена вручную или используйте переменные окружения Cargo.
Ручной подход с копированием:
$ cargo build --release && mkdir -p dist && cp target/release/my_app dist/
$ ls dist/
my_app
Скрипт деплоя:
#!/bin/bash
cargo build --release --out-dir dist
echo "Бинарники в dist/:"
ls dist/
Сохраните как deploy.sh
, сделайте исполняемым (chmod +x deploy.sh
) и запускайте: ./deploy.sh
.
Статическая линковка встраивает все зависимости (включая стандартную библиотеку и внешние библиотеки) в бинарник, делая его независимым от системы. Это полезно для деплоя на старые системы или в контейнеры вроде scratch
.
x86_64-unknown-linux-musl
)
По умолчанию Rust использует динамическую линковку со стандартной библиотекой (libstd
), которая зависит от системной libc
(например, glibc
). Для статической линковки используйте таргет x86_64-unknown-linux-musl
, основанный на musl
— легковесной статической C-библиотеке.
Шаги:
$ rustup target add x86_64-unknown-linux-musl
На Linux может потребоваться musl-tools
: sudo apt install musl-tools
.
$ cargo build --release --target x86_64-unknown-linux-musl
Finished release [optimized] target(s) in 2.12s
$ ls target/x86_64-unknown-linux-musl/release/
my_app ...
Бинарник теперь полностью статический. Проверим:
$ ldd target/x86_64-unknown-linux-musl/release/my_app
not a dynamic executable
Это значит, что он не требует внешних библиотек и будет работать на любой совместимой системе.
openssl
с vendored
)
Если проект использует crates с C-зависимостями (например, openssl
), они по умолчанию линкуются динамически. Для статической линковки используйте фичу vendored
, которая заставляет Cargo компилировать библиотеку самостоятельно.
Пример Cargo.toml
:
[dependencies]
openssl = { version = "0.10", features = ["vendored"] }
Сборка:
$ cargo build --release --target x86_64-unknown-linux-musl
$ ldd target/x86_64-unknown-linux-musl/release/my_app
not a dynamic executable
Фича vendored
загружает и компилирует OpenSSL
статически. Это требует build-dependencies
вроде cc
:
[build-dependencies]
cc = "1.0"
.cargo/config.toml
Для упрощения можно задать настройки в .cargo/config.toml
в корне проекта:
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
rustflags = ["-C", "link-arg=-static"]
Это указывает компоновщик и флаги для статической линковки. Теперь просто:
$ cargo build --release --target x86_64-unknown-linux-musl
ldd
)Полный процесс:
$ rustup target add x86_64-unknown-linux-musl
$ cargo build --release --target x86_64-unknown-linux-musl --out-dir dist
$ ls dist/
my_app
$ ldd dist/my_app
not a dynamic executable
$ du -h dist/my_app
2.3M dist/my_app
Сравним с динамической сборкой:
$ cargo build --release --out-dir dist_dynamic
$ ldd dist_dynamic/my_app
linux-vdso.so.1 ...
libc.so.6 ...
$ du -h dist_dynamic/my_app
1.8M dist_dynamic/my_app
Статический бинарник больше, но независим. Перенесите его на другую машину и проверьте:
$ scp dist/my_app user@remote:/tmp
$ ssh user@remote /tmp/my_app
--out-dir
для финальных сборок, чтобы не копировать вручную.build.rs
в проекты с несколькими бинарниками для автоматизации.musl
для контейнеров или старых систем.ldd
перед деплоем.strip
.--out-dir
и --target
в скриптах деплоя:
cargo build --release --target x86_64-unknown-linux-musl --out-dir dist
Теория — это важно, но настоящие навыки приходят с практикой. В этом разделе мы предлагаем вам небольшое упражнение, которое поможет закрепить знания о работе с зависимостями в Cargo. Вы добавите внешнюю библиотеку в проект, подключите её в коде и проверите результат. Это простая задача, но она охватывает ключевые шаги: настройку Cargo.toml
, импорт зависимостей и их использование в Rust. Давайте применим всё, что мы изучили, на практике — и заодно немного повеселимся с генерацией случайных чисел!
rand
и сгенерировать числоВаша цель — создать простое приложение, которое генерирует случайное число в заданном диапазоне и выводит его на экран. Для этого нужно:
rand
в проект как зависимость.src/main.rs
, который использует rand
для генерации числа от 1 до 100.
Библиотека rand
— это стандартный инструмент в Rust для работы со случайными числами. Она предоставляет удобные функции для генерации случайных значений, и мы будем использовать её базовую функциональность. Если у вас ещё нет проекта, создайте его с помощью cargo new random_app --bin
и следуйте инструкциям ниже.
Cargo.toml
и кодДавайте шаг за шагом решим задачу, добавив зависимость и написав код.
Шаг 1: Изменения в Cargo.toml
Откройте Cargo.toml
и добавьте rand
в секцию [dependencies]
. Мы будем использовать версию 0.8.5
, которая стабильна и широко поддерживается.
[package]
name = "random_app"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5" # Библиотека для генерации случайных чисел
После этого сохраните файл. Cargo автоматически загрузит rand
и её зависимости при следующей сборке. Мы указали точную версию "0.8.5"
, но можно использовать "^0.8"
для совместимых обновлений — это зависит от ваших предпочтений (см. раздел 3 о версиях).
Шаг 2: Код в src/main.rs
Теперь заменим содержимое src/main.rs
на код, который использует rand
для генерации случайного числа:
use rand::Rng; // Подключаем трейт Rng для генерации чисел
fn main() {
// Создаём генератор случайных чисел для текущего потока
let mut rng = rand::thread_rng();
// Генерируем случайное число от 1 до 100 (включительно)
let number = rng.gen_range(1..=100);
println!("Случайное число: {}", number);
}
Сохраните файл. Этот код:
Rng
из rand
для работы с генератором.thread_rng
— потокобезопасный генератор случайных чисел.gen_range
для генерации числа в диапазоне 1..=100
(включительно).println!
.Шаг 3: Сборка и запуск
Выполните команды в терминале:
$ cargo run
Compiling rand v0.8.5
Compiling random_app v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 1.67s
Running `target/debug/random_app`
Случайное число: 42
При первом запуске Cargo загрузит rand
, скомпилирует проект и выведет случайное число (ваше число будет отличаться!). Повторный запуск будет быстрее благодаря кешированию:
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/random_app`
Случайное число: 87
Давайте разберём, что мы сделали, и как это связано с изученным материалом.
Добавление зависимости:
rand = "0.8.5"
в [dependencies]
, как описано в разделе 2. Cargo автоматически загрузил crate с crates.io и записал точную версию в Cargo.lock
.Cargo.lock
:
$ cat Cargo.lock | grep rand
name = "rand"
version = "0.8.5"
Это обеспечивает воспроизводимость сборки (раздел 3).
Использование в коде:
use rand::Rng
подключает трейт, необходимый для метода gen_range
. Без этого компилятор выдаст ошибку, так как Rust требует явный импорт.thread_rng
— это удобный способ получить генератор, привязанный к текущему потоку. Он инициализируется автоматически с использованием системного источника случайности.gen_range(1..=100)
использует включающий диапазон (..=
), что означает числа от 1 до 100 включительно. Если написать 1..100
, верхняя граница будет 99.Сборка и запуск:
cargo run
(раздел 5) сначала вызывает cargo build
в профиле debug
, затем запускает бинарник. Вывод в target/debug/
можно изолировать с --out-dir
, как в разделе 7.rand
.Расширения (для любопытных):
release
:
$ cargo run --release --out-dir dist
Случайное число: 73
$ ls dist/
random_app
Бинарник будет быстрее и меньше.
small_rng
в Cargo.toml
:
rand = { version = "0.8.5", features = ["small_rng"] }
Это включит компактный генератор, хотя для простого примера разница незаметна.
Это упражнение — маленький, но важный шаг к освоению Cargo. Вы научились добавлять зависимости, подключать их в коде и видеть результат. Теперь вы можете экспериментировать с другими библиотеками, например, serde
для JSON или tokio
для асинхронности, применяя тот же подход.
Мы подошли к финалу главы, и это был насыщенный путь через одну из самых важных частей экосистемы Rust — Cargo. За это время мы разобрали инструмент до мельчайших деталей, превратив его из "чёрного ящика" в понятного и мощного помощника. Давайте подведём итоги того, что мы изучили, и посмотрим, как применить эти знания в ваших будущих проектах. А затем я приглашу вас не останавливаться на теории, а перейти к практике, чтобы закрепить всё, что вы узнали. Поехали!
Эта глава была настоящим глубоким погружением в Cargo, и мы охватили всё, что нужно для уверенной работы с проектами на Rust — от базовых настроек до продвинутых техник. Давайте вспомним ключевые моменты:
Cargo.toml
: Мы изучили, как задавать метаданные в [package]
, добавлять зависимости ([dependencies]
, [dev-dependencies]
, [build-dependencies]
) и управлять условной компиляцией через [features]
. Вы теперь знаете, как сделать проект готовым к публикации на crates.io или совместной разработке."^1.0"
, "~1.0.0"
) и ролью Cargo.lock
. Вы можете обновлять зависимости с cargo update
и анализировать их с помощью cargo tree
и cargo outdated
.debug
и release
, научились настраивать их в Cargo.toml
с параметрами вроде opt-level
и lto
, чтобы балансировать скорость компиляции и производительность.cargo build
, run
, test
и doc
, включая флаги вроде --out-dir
и --nocapture
. Теперь вы можете компилировать, тестировать и документировать проекты с лёгкостью.--out-dir
и build.rs
, а также собирать статические бинарники с x86_64-unknown-linux-musl
и фичами вроде vendored
для независимости от системы.rand
дало вам опыт добавления зависимостей и их использования в коде, связав теорию с реальной задачей.
Каждый из этих разделов — это кирпичик в фундаменте вашего мастерства работы с Cargo. Мы не просто прошлись по поверхности, а углубились в детали: от синтаксиса TOML до проверки статической линковки с ldd
. Теперь у вас есть полное представление о том, как Cargo управляет проектами, и вы можете настроить его под любые нужды — от маленьких скриптов до крупных приложений.
Cargo — это не просто инструмент, а ваш союзник в разработке. Он берёт на себя рутину — загрузку зависимостей, компиляцию, тестирование — и позволяет сосредоточиться на самом важном: создании качественного кода. Эта глава дала вам карту и компас для навигации по его возможностям, и теперь вы готовы использовать их в полную силу.
Знания без практики — как книга, которую вы прочитали, но не применили. Мы подробно разобрали Cargo, но настоящий прогресс начнётся, когда вы возьмёте эти идеи и начнёте экспериментировать. Вот несколько идей, чтобы вдохновить вас на дальнейшие шаги:
rand
или утилиту для чтения файлов с std::fs
. Добавьте зависимости, настройте профили и попробуйте статическую линковку.pretty_assertions
для красивого вывода ошибок и проверьте документационные тесты с cargo test
.Cargo.toml
и работу с метаданными.build.rs
для копирования бинарников или генерации кода. Попробуйте интегрировать это в CI/CD, например, GitHub Actions.x86_64-unknown-linux-musl
или даже wasm32-unknown-unknown
для веба. Проверьте бинарники на другой машине или в браузере.
Не бойтесь ошибаться — компилятор Rust и Cargo помогут вам найти и исправить проблемы. Каждый запуск cargo build
, каждая строка в Cargo.toml
— это шаг к тому, чтобы стать уверенным разработчиком. Попробуйте что-то своё: может быть, утилиту для командной строки или сервер на tokio
. Главное — начните!
Эта глава — не конец, а начало. Cargo — это инструмент, который будет с вами на всём пути изучения Rust, и теперь у вас есть знания, чтобы использовать его эффективно. Откройте терминал, создайте новый проект с cargo new
и дайте волю своему творчеству. Мы прошли теорию вместе, а практика — за вами. Удачи в ваших Rust-приключениях, и до встречи в следующей главе!