Привет, Хабр!
На дворе 2025, и у каждого языка свой подход к сборке, зависимостям и публикации. В Rust за это отвечает Cargo — инструмент, который берёт на себя всё: от менеджмента зависимостей до тестов, бенчмарков и выкладки на crates.io.
И вот это мы и рассмотрим в статье: как устроен Cargo изнутри, зачем нужен Cargo.toml
, как подключать зависимости, куда падают артефакты сборки, что делает cargo check
, как запускать и бенчмаркать, и как наконец создать свой крейт на crates.io.
Что такое Cargo и зачем тебе Cargo.toml
Cargo — это не просто «сборщик» или «менеджер зависимостей». Это единая точка входа в Rust‑проект: от инициализации и сборки — до тестов, бенчей, зависимостей и публикации.
Он появился не «потому что надо как у pip/npm» — а потому что в Rust иначе нельзя. Язык строго типизирован, компилируемый, с сильной моделью зависимостей и zero‑cost‑абстракциями. Без этого инструмента разработка на Rust быстро превращалась бы в болото из rustc
‑флагов, ручного подключения крейтов, проблем с совместимостью и нестабильных билдов. Именно поэтому Cargo стал частью экосистемы с самого начала — не опциональной, а встроенной.
Можно, конечно, скомпилить .rs
вручную через rustc
, но это как запускать Kubernetes через bash
‑скрипты. Cargo стандартизирует:
структуру проекта;
формат манифеста;
процесс сборки;
работу с зависимостями;
тестирование, бенчмарки и документацию;
публикацию на crates.io.
И главное — он делает это консенсусно. Все Rust‑проекты выглядят одинаково, собираются одинаково и ведут себя одинаково.
Файл Cargo.toml
— это ядро проекта. Это не просто файл с зависимостями, как requirements.txt
или package.json
. Это декларативный API проекта, через который Cargo понимает:
как его зовут,
какой у него edition,
какие зависимости он использует (в том числе dev, build, optional),
какие features включены,
где исходники,
какие есть бинарники,
что класть в публикацию и т. д.
И именно Cargo.toml
позволяет создавать то, что в других языках потребовало бы 10 конфигов, 3 генератора и один build.rs
.
Допустим, у нас есть проект. Пишем на Rust. У нас обязан быть Cargo.toml
в корне. Это manifest
:
[package]
name = "my_cool_app"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
Здесь объявляется всё:
имя крейта,
версия,
Rust edition (
2015
,2018
,2021
— сейчас это важно, особенно для async/await),зависимости и их фичи.
Хочешь dev‑зависимости (тесты, бенчи)? Просто добавь:
[dev-dependencies]
criterion = "0.5"
Нужна сборка на этапе компиляции?
[build-dependencies]
cc = "1.0"
Нужен workspace из нескольких крейтов:
[workspace]
members = [
"core-lib",
"cli",
"server",
]
И никаких requirements.txt
, setup.py
, package.json
— всё в одном файле.
Несколько полезных крейтов:
serde
/serde_json
— сериализация/десериализация в любые форматы (JSON, TOML, YAML и т. д.).anyhow
— удобная работа с ошибками без драмы, с контекстами и backtrace.thiserror
— макросы для описания собственных ошибок без боли.log
+env_logger
/tracing
— логирование. Первый — классика, второй — современный и async‑friendly.reqwest
— полноценный HTTP‑клиент на базеhyper
.tokio
/async-std
— async runtime'ы. Если нужен асинхрон, без них никуда.clap
/argh
— для CLI‑приложений, парсинг аргументов без шаманства.rayon
— параллельные итераторы: хочешь использовать все ядра без ручногоthread::spawn
? Вот оно.regex
— регулярки, быстрые и удобные.crossbeam
— адекватные примитивы многопоточности.lazy_static
/once_cell
— глобальные константы и ленивая инициализация.dashmap
— конкурентныйHashMap
.uuid
— генерация UUID'ов всех форматов.
Как подключать зависимости правильно
Cargo работает с crates.io по умолчанию. Указал имя и версию — и поехали:
[dependencies]
chrono = "0.4"
Нужен git?
serde_json = { git = "https://github.com/serde-rs/json", branch = "main" }
Локально?
my_utils = { path = "../utils" }
Нужно включить только определённые фичи?
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
Мы сами решаем, как строится граф зависимостей. Cargo сам скачает, прокаеширует, откомпилит, положит в ~/.cargo
и target/
.
Как устроен процесс сборки: .cargo, target/, кеш, артефакты
Когда мы вызываем cargo build
, происходит следующее:
Cargo читает
Cargo.toml
+Cargo.lock
.Скачивает все зависимости (если ещё не в кеше).
Кладёт исходники зависимостей в
~/.cargo/registry/src/
.Компилирует каждый dependency в виде
.rlib
,.d
,.rmeta
и так далее.Кладёт всё в
target/debug/deps/
.
Где и что хранится?
project/
├── Cargo.toml
├── Cargo.lock
├── src/
│ └── main.rs
└── target/
├── debug/
│ ├── deps/ # Все .rlib и бинарники
│ └── build/ # Вывод сборки build.rs
├── release/ # Оптимизированный билд
└── incremental/ # Кеш для повторной сборки
Бинарник будет здесь:
target/debug/my_cool_app
А если:
cargo build --release
То здесь:
target/release/my_cool_app
cargo check
Запускает все фазы компиляции до линковки. То есть:
Проверяет синтаксис — как
rustc
, но быстрее.Запускает анализ типов — включая вывод типов (
let x = ...
без аннотации? не проблема).Гоняет
borrow checker
— чтобы сразу можно было увидеть все «cannot borrowfoo
as mutable» и прочие прелести.Выполняет разбор lifetime'ов — чтобы не втыкать потом, где
'a
конфликтует с'static
.
Но: он не создаёт бинарник, не линкует и не тратит время на всякое лишнее.
Когда его использовать?
Всегда, когда:
поменял тип поля в структуре — и хочется проверить, где ещё всё отвалится;
рефакторишь сигнатуру функции;
быстро фиксим compile‑time ошибки;
хочется мгновенную обратную связь на каждом сохранении (он почти как
tsc --noEmit
для Rust).
cargo check
Результат:
Checking my_cool_app v0.1.0 (/home/me/projects/my_cool_app)
error[E0308]: mismatched types
--> src/main.rs:7:20
|
7 | let name: i32 = "Ivan";
| ^^^^^^^^ expected `i32`, found `&str`
И бонус: работает с features, таргетами и workspace'ами
Хочется проверить только с определёнными фичами:
cargo check --features "tls async"
Проверка для релизной сборки:
cargo check --release
Таргет на ARM:
cargo check --target armv7-unknown-linux-gnueabihf
cargo run: как запускать с аргументами и в разных окружениях
Команда:
cargo run -- --config ./config.yml
Выполняется как:
cargo build
./target/debug/my_cool_app --config ./config.yml
Можно:
запускать конкретный бинарь (если их несколько):
cargo run --bin server
запускать с фичами:
cargo run --features "use_tls"
запускать для другого таргета (например, armv7
):
cargo run --target armv7-unknown-linux-gnueabihf
cargo bench
В стандартной библиотеке есть поддержка бенчей, но она требует nightly. Пример:
#![feature(test)]
extern crate test;
use test::Bencher;
#[bench]
fn bench_sum(b: &mut Bencher) {
let data: Vec<u64> = (1..1_000_000).collect();
b.iter(|| data.iter().sum::<u64>());
}
Если не любим nightly, берем criterion.rs:
[dev-dependencies]
criterion = "0.5"
use criterion::{criterion_group, criterion_main, Criterion};
fn bench_add(c: &mut Criterion) {
c.bench_function("sum 1..1000", |b| {
b.iter(|| (1..1000).sum::<u64>());
});
}
criterion_group!(benches, bench_add);
criterion_main!(benches);
Запуск:
cargo bench
Как написать свой крейт и выложить его на crates.io
Зарегистрироваться
Переходим на crates.io. Логинимся через GitHub. Переходм в /me
→ генерируем API Token
cargo login <токен>
Cargo.toml
[package]
name = "fastmath"
version = "0.1.0"
edition = "2021"
description = "Fast math utilities for Rust"
license = "MIT OR Apache-2.0"
repository = "https://github.com/user/fastmath"
readme = "README.md"
keywords = ["math", "fast", "utils"]
categories = ["algorithms"]
Добавляем README.md и LICENSE
touch README.md
echo "MIT" > LICENSE
Проверка и публикация
cargo package # Проверит, что всё ок
cargo publish # И только потом выкладывай
После этого крейт доступен вот так:
[dependencies]
fastmath = "0.1"
Заключение
У других языков свои инструменты. У Rust — Cargo. Он простой, мощный и делает всё, что нужно: зависимости, сборка, тесты, бенчи, публикация.
А если вы ещё не писали свои крейты — самое время начать.
Если вы хотите углубить свои знания о Rust и его возможностях, приглашаем на серию открытых уроков, где вы сможете разобраться в ключевых аспектах языка и применении его на практике. Записывайтесь по ссылкам ниже: