$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогCargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

$ sudo teach IT
·18 сентября 2026 г.·10 мин·99
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

Содержание
Организация workspace для продакшенаЗачем workspace вообще нуженМинимальная структураworkspace.dependencies и единая база зависимостейСегментация: общие crates и сервисыFeature flags: optional features без хаосаПочему feature-флаги “взрываются” в командахfeature gating внутри вашего кодаDefault features: что делать в продакшенеКак включать features в workspace-стратегииУправление features у внешних зависимостейСтратегия версий зависимостей и контроль “дрейфа”Что такое дрейф и почему он опасенCargo.lock в репозиторииРазумные ограничения в Cargo.tomlОбновление зависимостей без хаоса: “ритуал PR”Как избежать “разных lock-файлов” в CIМатрица сборки: что тестировать, чтобы feature- и dependency-ошибки не просачивалисьКакие “ось” имеет реальность в продакшенеПрактический минимумPodvodные камни и типичные ошибки1) Слишком “широкие” feature-флаги2) “Default” делает всё незаметным3) Обновления зависимостей “вразнобой”4) Несогласованность resolver’аПрактический шаблон: как собрать всё вместеКорневой workspaceОбщая библиотека с featuresСервис выбирает feature setCI команды (концептуально)Вывод: Cargo как система дисциплины, а не набор команд

В реальном Rust-проекте “просто cargo build” быстро превращается в систему с нюансами: несколько сервисов и библиотек, общие компоненты, разные конфигурации сборки, плагины и внутренние API. От того, как вы организуете workspace, как управляете feature-флагами и как фиксируете версии зависимостей, зависит не только удобство разработки, но и стабильность CI, предсказуемость релизов и стоимость поддержки.

Ниже — практический разбор того, как подходить к Cargo в контексте продакшена: архитектура workspace, работа с optional features без хаоса, контроль дрейфа зависимостей и “стратегия версий”, которая выдерживает командную разработку.


Организация workspace для продакшена

Зачем workspace вообще нужен

Cargo workspace — способ сгруппировать несколько пакетов (crates) под одним “корнем”. Типовые причины использовать workspace в продакшене:

  • Единое управление зависимостями для набора пакетов: удобно фиксировать версию общей библиотеки или внутренних общих хелперов.
  • Оркестрация CI: сборка и тесты “по всем” или по конкретным пакетам.
  • Согласованный релиз: вы публикуете библиотеки или делаете версии внутри компании.
  • Уменьшение дублирования: общие зависимости/конфигурации вынести в workspace-level настройки (и хотя бы по привычке держать их единообразно).

Минимальная структура

Обычно удобно начинать со структуры:

code
repo/
  Cargo.toml
  crates/
    auth/
      Cargo.toml
      src/...
    api/
      Cargo.toml
      src/...
    common/
      Cargo.toml
      src/...
  services/
    users-service/
      Cargo.toml
      src/...
    billing-service/
      Cargo.toml
      src/...

Корневой Cargo.toml — workspace-скелет:

code
[workspace]
members = [
  "crates/auth",
  "crates/api",
  "crates/common",
  "services/users-service",
  "services/billing-service",
]
resolver = "2"

Ключевые моменты:

  • resolver = "2" — современный резолвер зависимостей, корректнее учитывающий feature-граф (важно для “дрейфа” и конфликтов).
  • members — список пакетов. В продакшене часто добавляют “явный” список вместо glob, чтобы не тянуть случайные папки.

workspace.dependencies и единая база зависимостей

В Rust 1.70+ активно используют механизм зависимостей на уровне workspace через [workspace.dependencies]. Это позволяет задать версию один раз и ссылаться на неё в пакетах. Например:

code
# repo/Cargo.toml
[workspace.dependencies]
serde = { version = "1.0.197", features = ["derive"] }
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1.40"

А в конкретном crate:

code
# repo/crates/common/Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

Плюсы подхода:

  • снижаете шанс, что разные сервисы потянут разные версии одной и той же библиотеки из-за “копипасты”;
  • проще обновлять версии централизованно;
  • делаете граф зависимостей более предсказуемым для CI.

Сегментация: общие crates и сервисы

Хорошая практическая схема:

  • crates/* — библиотеки, которые переиспользуются;
  • services/* — бинарники (executables), которые собирают библиотеки и связывают инфраструктуру: конфиг, транспорт, внешние клиенты.

Это полезно ещё и для “feature-профилей” (об этом ниже): сервисы чаще включают дополнительные фичи (например, server, metrics, integration-tests), а библиотеки держат более строгую базу.


Feature flags: optional features без хаоса

Почему feature-флаги “взрываются” в командах

Feature-флаги в Cargo — мощный механизм, но в продакшене они часто становятся источником проблем:

  • разные команды включают разные features для одной и той же зависимости;
  • в dependency graph растёт количество комбинаций;
  • CI начинает “флапать”, потому что разные пакеты в разное время собираются с разными feature set’ами;
  • появляются скрытые зависимости: код компилируется только при определённом флаге, но никто не тестирует это в CI.

Задача — управлять feature-графом системно.

feature gating внутри вашего кода

Представьте, что вы делаете библиотеку common, и в ней есть опциональная интеграция с базой:

code
# crates/common/Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2021"

[features]
default = ["client-http"]
client-http = ["dep:reqwest"]
client-grpc = ["dep:tonic"]

[dependencies]
reqwest = { version = "0.12", optional = true }
tonic = { version = "0.11", optional = true }

В коде:

code
// crates/common/src/lib.rs
#[cfg(feature = "client-http")]
pub mod http_client;

#[cfg(feature = "client-grpc")]
pub mod grpc_client;

Важно: features должны быть “ортогональными” по смыслу. Если вы делите всё на мелкие флаги (“logging”, “tracing”, “metrics”, “auth”), но они пересекаются и образуют сложные комбинации — усложняется матрица тестирования.

Default features: что делать в продакшене

Многие проекты оставляют default = [] и явно включают нужные фичи на уровне сервисов. Это снижает риск случайно включить тяжёлую функциональность.

В приведённом примере мы используем default = ["client-http"]. Это допустимо, если HTTP — действительно “умолчание”, которое тестируется и поддерживается.

Но в продакшене разумнее формулировать policy:

  • библиотека: default минимальный (или явно тестируемый);
  • сервис: выбирает набор features под свою конфигурацию (prod/dev/worker/integration).

Как включать features в workspace-стратегии

Поскольку сервисы являются “главными” точками сборки, удобно включать фичи именно там:

code
# services/users-service/Cargo.toml
[dependencies]
common = { path = "../../crates/common", features = ["client-http"] }

А если вы хотите, чтобы staging включал дополнительные возможности:

code
# services/users-service/Cargo.toml
[features]
default = ["prod"]
prod = ["common/client-http"]
staging = ["common/client-http", "common/metrics"]

# и далее: common должен иметь feature metrics

Сделайте так, чтобы CI запускал минимум один “prod” и один “staging” режим. Иначе feature-флаги останутся теоретическими.

Управление features у внешних зависимостей

Отдельная дисциплина — как вы включаете features чужих crates.

Например, вы хотите включить serde derive во всех местах:

  • либо задаёте это в [workspace.dependencies] для serde = { features = ["derive"] };
  • либо оставляете зависимость без derive и включаете его точечно в crate, который реально использует Serialize/Deserialize.

Первый вариант проще, но может тянуть “лишнее” туда, где вы не используете derive (хотя компилятор и устраняет мёртвый код, зависимость/сборочная конфигурация всё равно усложняется).

В продакшене чаще выбирают “минимальные зависимости” на уровне каждой библиотеки, а на уровне сервисов включают необходимые фичи для конкретного стека.


Стратегия версий зависимостей и контроль “дрейфа”

Что такое дрейф и почему он опасен

Cargo разрешает версии по семантическим правилам. Если вы в Cargo.toml пишете:

code
serde = "1.0"

то при обновлении lock-файла вы можете получить новые патч/минор-версии, которые формально совместимы, но меняют:

  • поведение (иногда даже в рамках совместимости);
  • типы в edge-case (например, через feature-граф);
  • транзитивные зависимости и их security-патчи/багфиксы.

В команде особенно неприятны ситуации, когда:

  • локально у вас один Cargo.lock,
  • в CI собирается другой граф из-за несогласованного lock-файла или изменения условий сборки,
  • при этом тесты проходят “в одном режиме”, но падают “в другом”.

Cargo.lock в репозитории

Решение обычно одно: фиксировать Cargo.lock в репозитории для приложений (bin crates). Для библиотек публикация Cargo.lock обычно не требуется — чаще используют semver и не фиксируют downstream.

Практическое правило:

  • если у вас много сервисов, каждый сервис-бинарник при сборке порождает/обновляет Cargo.lock. В workspace удобно держать один lock-файл в корне репозитория и обновлять его централизованно.
  • если у вас чисто библиотеки, lock-файл можно исключать, но в продакшене чаще всё равно нужен для тестовых сборок в CI (решается настройками).

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

Разумные ограничения в Cargo.toml

Есть три уровня контроля.

  1. Только Cargo.lock (самый распространённый): версии в Cargo.toml слабо ограничены, но lock фиксирует фактический набор. Обновление зависимостей делается отдельным PR.
  2. Уточнение диапазонов: вы сужаете диапазон версий, снижая вероятность “скачков” при обновлении lock.
  3. Глубокий контроль через “patch/replace”: вы принудительно указываете конкретную версию для конкретного пакета (или фичу/форк).

Для командной работы обычно хватает сочетания (1) + (2): держите lock и обновляйте dependency graph по расписанию или по событию (security/баг).

Обновление зависимостей без хаоса: “ритуал PR”

Команда обычно приходит к процессу:

  • раз в N недель делают PR “Update dependencies” (например, cargo update -p <crate> или просто cargo update);
  • обязательно прогоняют:
    • cargo test (минимум всех пакетов/профилей),
    • сборку всех targets,
    • unit/integration (в зависимости от матрицы).
  • если CI поддерживает несколько feature-режимов — обновления делают один раз, а затем проверяют весь матрикс.

Тонкий момент: cargo update обновляет lock-файл, но не меняет ваши требования в Cargo.toml. Если вы хотите “перейти на новую major” — потребуется правка Cargo.toml.

Как избежать “разных lock-файлов” в CI

Типичные причины, почему CI начинает жить “с другим миром”:

  • в CI не используется репозиторный lock (например, --locked не применяется);
  • lock-файл игнорируется или перетирается;
  • разные job-матрицы используют разные features, из-за чего Cargo может собирать разные графы (но lock один; при этом feature selection определяет набор compiled deps и может повлиять на тестируемость).

Минимальный подход: в CI включайте --locked:

code
cargo test --workspace --locked
cargo build --workspace --locked

--locked заставляет Cargo не обновлять Cargo.lock во время сборки. Это превращает dependency дрейф из “скрытого” фактора в “явный PR”.


Матрица сборки: что тестировать, чтобы feature- и dependency-ошибки не просачивались

Какие “ось” имеет реальность в продакшене

В продакшене у вас обычно есть минимум две оси:

  1. Profile: dev/test/prod (разные feature set’ы и иногда разные зависимости).
  2. Бинарники/пакеты: часть crates может использоваться только одним сервисом.

Если вы тестируете только cargo test --workspace с default features, вы защищены лишь от части проблем. Feature-ошибка — это почти всегда “компилируется только при определённом наборе flags”.

Практический минимум

Рекомендуемый минимум для команды:

  • собрать и протестировать default конфигурацию (то, что максимально приближено к “prod по умолчанию”);
  • отдельно прогнать один “non-default” feature набор (например, --no-default-features или --features staging);
  • прогнать базовую сборку release (хотя бы cargo build --workspace --release --locked), чтобы ловить различия оптимизаций.

Пример (на уровне shell-script в CI):

code
set -euo pipefail

# База: то, что должно совпадать с продом
cargo test --workspace --locked

# Проверяем alternative feature-set
cargo test --workspace --locked --no-default-features --features "client-grpc"

# Для компиляции релизом
cargo build --workspace --release --locked

Если матрица большая, начните с малого и постепенно расширяйте.


Podvodные камни и типичные ошибки

1) Слишком “широкие” feature-флаги

Когда feature включает половину мира, его невозможно корректно тестировать. В результате вы получаете “работает только в идеальных условиях”.

Решение: дробить features по смыслу и вводить policy: каждый публичный feature-флаг должен быть протестирован хотя бы в одном job.

2) “Default” делает всё незаметным

Если библиотека имеет default = ["heavy-feature-a", "heavy-feature-b"], то сервисы по умолчанию тянут лишнее. Это усложняет старт и повышает вероятность конфликтов версий (особенно с транзитивными dependency features).

Решение: default либо минимальный, либо хорошо документированный и проверяемый.

3) Обновления зависимостей “вразнобой”

Когда разработчики обновляют lock-файл в своих PR без общей стратегии, команда начинает жить с “несовместимыми графами”. Даже если версию у вас одна и та же, пересборка с разными фичами может проявить разные ошибки.

Решение: один тип PR “Update dependencies”, обязательные проверки, и запрет автоматического изменения lock-файла в CI через --locked.

4) Несогласованность resolver’а

Если один проект/пакет живёт с resolver = "1", а другой — с resolver = "2", вы получаете трудные для диагностики отличия в feature resolution.

Решение: на уровне workspace всегда задавайте resolver = "2".


Практический шаблон: как собрать всё вместе

Ниже — компактный пример “рабочей” схемы:

Корневой workspace

code
# Cargo.toml (repo)
[workspace]
members = [
  "crates/common",
  "services/users-service",
]
resolver = "2"

[workspace.dependencies]
serde = { version = "1.0.197", features = ["derive"] }
tracing = "0.1.40"

Общая библиотека с features

code
# crates/common/Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2021"

[features]
default = ["client-http"]
client-http = ["dep:reqwest"]
client-grpc = ["dep:tonic"]

[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
reqwest = { version = "0.12", optional = true }
tonic = { version = "0.11", optional = true }

Сервис выбирает feature set

code
# services/users-service/Cargo.toml
[package]
name = "users-service"
version = "0.1.0"
edition = "2021"

[dependencies]
common = { path = "../../crates/common", features = ["client-http"] }

CI команды (концептуально)

code
cargo test --workspace --locked
cargo test --workspace --locked --no-default-features --features "client-grpc"
cargo build --workspace --release --locked

В реальности команды будут чуть сложнее из-за различий между пакетами, но логика сохраняется: фиксированный lock, осознанный feature set, минимальная матрица проверок.


Вывод: Cargo как система дисциплины, а не набор команд

Продакшен на Rust — это не только код. Это набор “инфраструктурных” решений: как вы организовали workspace, как структурировали feature-флаги, насколько вы контролируете обновления зависимостей и как CI превращает сборку в воспроизводимый процесс.

Если обобщить:

  • workspace используйте, чтобы централизовать граф проекта и облегчить CI/релизы.
  • feature flags держите семантически ясными и тестируйте хотя бы базовые режимы (prod и один alternative).
  • версии зависимостей фиксируйте через Cargo.lock в репозитории и включайте --locked в CI, а обновления делайте управляемыми PR’ами.
  • математику комбинаций не раздувайте: чем меньше неконтролируемых фич — тем предсказуемее релизы.

Если вы только начинаете выстраивать эту дисциплину или хотите быстрее “переварить” базовую механику Cargo (workspaces, features, lock-файлы и командами), полезной отправной точкой может стать курс Cargo – для начинающих!. Он помогает системно разложить то, что в продакшене обычно приходится собирать по кускам.

А дальше — уже практика: репозиторий, CI, осознанные feature set’ы и спокойное обновление зависимостей без дрейфа “по воздуху”.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Cargo – для начинающих!

Курс по теме

Cargo – для начинающих!

С нуля до уверенной работы с Rust-проектами. Подходит для начинающих и тех, кто уже пишет на Rust. Разберитесь с Cargo раз и навсегда — и перестаньте копировать команды вслепую.

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Приложения для iPhone и Apple Watch на SwiftUI

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Приложения для macOS на SwiftUI

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

Паттерн «Стратегия»: замените гигантские if-else одним элегантным решением
паттерн

Паттерн «Стратегия»: замените гигантские if-else одним элегантным решением

Разбираем поведенческий паттерн Strategy на примерах на Python и Java: когда он нужен, как его реализовать и чем он отличается от простого полиморфизма.

16 июля 2026 г.
680
Rust Cargo для команды: workspace, общие зависимости и единые версии библиотек
rust

Rust Cargo для команды: workspace, общие зависимости и единые версии библиотек

Покажем, как организовать монорепо без хаоса: workspace, shared crates, настройка версий и feature flags. Статья поможет уменьшить “сюрпризы” при сборке на разных машинах.

26 июля 2026 г.
580
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1421
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
570
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1060
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

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

2 октября 2026 г.
610

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Организация workspace для продакшенаЗачем workspace вообще нуженМинимальная структураworkspace.dependencies и единая база зависимостейСегментация: общие crates и сервисыFeature flags: optional features без хаосаПочему feature-флаги “взрываются” в командахfeature gating внутри вашего кодаDefault features: что делать в продакшенеКак включать features в workspace-стратегииУправление features у внешних зависимостейСтратегия версий зависимостей и контроль “дрейфа”Что такое дрейф и почему он опасенCargo.lock в репозиторииРазумные ограничения в Cargo.tomlОбновление зависимостей без хаоса: “ритуал PR”Как избежать “разных lock-файлов” в CIМатрица сборки: что тестировать, чтобы feature- и dependency-ошибки не просачивалисьКакие “ось” имеет реальность в продакшенеПрактический минимумPodvodные камни и типичные ошибки1) Слишком “широкие” feature-флаги2) “Default” делает всё незаметным3) Обновления зависимостей “вразнобой”4) Несогласованность resolver’аПрактический шаблон: как собрать всё вместеКорневой workspaceОбщая библиотека с featuresСервис выбирает feature setCI команды (концептуально)Вывод: Cargo как система дисциплины, а не набор команд