Cargo — workspace, features и профили
Cargo — workspace, features и профили
Зачем углубляться в Cargo
После первой программы вы уже знаете cargo new и cargo run. Реальные проекты редко укладываются в один файл: появляются библиотека + бинарник, несколько crate в одном репозитории, опциональные модули (features), разные режимы оптимизации (profiles), иногда скрипт build.rs перед компиляцией.
Cargo — единая точка: зависимости, сборка, тесты, документация. Понимание манифеста Cargo.toml экономит часы при отладке «почему не собралось на CI».
Обзор инструментов: фреймворки и Cargo. Синтаксис: справочник.
Словарь
| Термин | Значение |
|---|---|
| Package (пакет) | То, что описано одним Cargo.toml — имя, версия, зависимости. |
| Crate | Единица компиляции: библиотека (rlib) или исполняемый файл. |
| Workspace | Несколько пакетов в одном репозитории с общим Cargo.lock. |
| Feature | Флаг условной компиляции и опциональных зависимостей. |
| Profile | Набор настроек компилятора: dev, release, test. |
| Cargo.lock | Зафиксированные точные версии зависимостей (для приложений коммитят в git). |
Один пакет — бинарник и библиотека вместе
Типичный сервис:
my-app/
Cargo.toml
src/
main.rs # точка входа: cargo run
lib.rs # логика, pub API, тесты
Cargo.toml:
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
[[bin]]
name = "my-app"
path = "src/main.rs"
По соглашению Cargo и так ищет src/main.rs и src/lib.rs; секция [[bin]] нужна, если бинарников несколько или путь нестандартный.
Практика: вся логика в lib.rs (pub fn run()), main.rs только вызывает my_app::run(). Тогда интеграционные тесты в tests/ подключают библиотеку как внешний клиент.
Для динамической библиотеки под FFI:
[lib]
crate-type = ["cdylib"]
Собирается .dll / .so / .dylib — см. FFI.
Workspace (монорепозиторий)
Несколько связанных crate в одном git-репозитории:
acme/
Cargo.toml # корень: [workspace]
crates/
core/
Cargo.toml
src/lib.rs
api/
Cargo.toml
src/main.rs
Корневой Cargo.toml:
[workspace]
resolver = "2"
members = [
"crates/core",
"crates/api",
]
[workspace.package]
edition = "2021"
version = "0.1.0"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
resolver = "2"— современные правила разрешения features зависимостей (рекомендуется для новых workspace).[workspace.dependencies]— общий каталог версий; в дочерних пакетах пишутserde = { workspace = true }.
crates/api/Cargo.toml:
[package]
name = "acme-api"
[dependencies]
acme-core = { path = "../core" }
tokio = { workspace = true }
serde = { workspace = true }
Команды из корня репозитория:
cargo build -p acme-api
cargo test --workspace
cargo run -p acme-api
Преимущества: один Cargo.lock, переиспользование кода через path = "..." без публикации на crates.io, согласованные версии tokio / serde во всех crate.
Features (условная сборка)
Feature — имя переключателя в Cargo.toml. Он может включать зависимость и куски кода через #[cfg(feature = "...")].
[dependencies]
reqwest = { version = "0.12", optional = true }
[features]
default = ["http"]
http = ["dep:reqwest"]
optional = true— зависимость тянется только если включена feature.default— что включено при обычномcargo build.http = ["dep:reqwest"]— синтаксис Cargo 1.60+: явная привязка feature к зависимости.
В коде:
#[cfg(feature = "http")]
pub fn fetch(url: &str) -> Result<String, reqwest::Error> {
reqwest::blocking::get(url)?.text()
}
Сборка:
cargo build --no-default-features
cargo build --features http
cargo test --all-features
Важно для дизайна: features должны быть аддитивными (включение не ломает чужой код). Другой crate в графе зависимостей может снова включить вашу feature — полагаться на «мы её выключили» нельзя. Взаимоисключающие режимы лучше разнести по разным crate или документировать один главный feature.
Профили — dev, release, test
Профиль — набор флагов компилятора в Cargo.toml:
[profile.dev]
opt-level = 0
debug = true
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
| Команда | Профиль | Назначение |
|---|---|---|
cargo build | dev | Быстрая итерация, много отладочной информации |
cargo build --release | release | Продакшен: оптимизация, меньший бинарник |
cargo test | test | Как dev + отладка для тестов |
opt-level— степень оптимизации (0 — почти без оптимизаций).lto— link-time optimization, дольше сборка, иногда меньше и быстрее бинарник.codegen-units = 1в release — одна единица генерации кода, лучше оптимизация, дольше компиляция.
Кастомный профиль для бенчмарков:
[profile.bench]
inherits = "release"
debug = true
Зависимости — секции и версии
[dependencies]
anyhow = "1"
thiserror = "2"
[dev-dependencies]
tokio = { version = "1", features = ["rt", "macros"] }
[build-dependencies]
cc = "1"
| Секция | Когда используется |
|---|---|
dependencies | Основной код |
dev-dependencies | Только тесты, примеры, бенчмарки |
build-dependencies | Только скрипт build.rs |
Версия "1" на crates.io означает semver: совместимы обновления 1.x.y, но не 2.0.0. cargo update пересчитывает Cargo.lock. Для библиотек, публикуемых на crates.io, lock-файл обычно не коммитят; для приложений — коммитят, чтобы CI и коллеги собирали одинаково.
Совет: у тяжёлых зависимостей отключайте лишние default-features — ускоряет сборку у всех, кто подключит ваш crate.
build.rs — скрипт до компиляции
Файл build.rs в корне пакета Cargo запускает перед компиляцией Rust-кода. Типичные задачи:
- собрать C/C++ через
cc; - сгенерировать код (bindgen, protobuf);
- передать линковщику пути:
println!("cargo:rustc-link-search=..."); - сказать Cargo пересобрать при изменении файла:
println!("cargo:rerun-if-changed=...").
Пример:
// build.rs
fn main() {
cc::Build::new()
.file("native/add.c")
.compile("native_add");
println!("cargo:rerun-if-changed=native/add.c");
}
Подробнее про связывание с C — FFI на практике.
Полезные команды
cargo tree -p my-app # дерево зависимостей
cargo check # проверка типов без полной линковки (быстрее build)
cargo doc --open # локальная документация зависимостей и вашего кода
cargo clippy -- -D warnings # линтер
cargo fmt # единый стиль форматирования
Связанные материалы
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). История Rust - путь языка от эксперимента до промышленного стандарта безопасного системного программирования. Rust — это многопарадигменный язык программирования общего назначения, который фокусируется на безопасности памяти, производительности и параллелизме. Набор советов, правил, принципов и обычаев в разработке на этом языке. Трейты могут иметь методы по умолчанию. Если тип не переопределяет метод, используется версия из трейта. Это позволяет расширять функциональность без изменения базового кода. Простые приложения на Rust — CLI, файлы, JSON и минимальный HTTP на stdlib и tokio. fn - ключевое слово, которое обозначает начало объявления функции. Функция представляет собой именованный блок кода, выполняющий конкретную задачу. В данном случае функция называется main. Экосистема приложений на Rust - направления применения языка и ключевые инструменты промышленной разработки. Системное программирование на Rust - низкоуровневый контроль, безопасность памяти и надёжные инфраструктурные компоненты. Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевые слова Rust - назначение операторов и зарезервированных идентификаторов в синтаксисе языка. Встроенные функции и стандартная библиотека Rust - базовые макросы, атрибуты и инструменты тестирования. Типизация, набор правил определения типа данных значений языка.История языка Rust
Что требуется знать перед началом изучения языка программирования Rust
Рекомендации по разработке на Rust
Rust для начинающих
Простые приложения на Rust
Основы языка Rust
Экосистема приложений на Rust
Системное программирование на Rust
Синтаксис и пунктуация в Rust
Ключевые слова языка Rust
Встроенные функции и стандартная библиотека
Типы данных и владение памятью