Введение
С ростом проекта на Rust возникает вопрос: как организовать код, чтобы он оставался поддерживаемым, масштабируемым и удобным для команды? Один из популярных подходов — монорепозиторий (monorepo). В этой статье мы разберем, что такое монорепозиторий, как его организовать с помощью Cargo workspace, и приведем практические примеры. Вы узнаете, как структурировать код, управлять зависимостями и избежать типичных ошибок.
Что такое монорепозиторий и зачем он нужен в Rust?
Монорепозиторий (или монорепо) — это единый репозиторий, который содержит несколько проектов (крейтов) или пакетов. В отличие от мультирепозиториев, где каждый проект живет отдельно, монорепозиторий объединяет всё: библиотеки, бинарники, тесты, конфигурации и документацию.
Преимущества монорепозитория для Rust:
- Единая версия зависимостей — все крейты используют одинаковые версии внешних пакетов, что снижает конфликты.
- Упрощение рефакторинга — изменения в одной библиотеке сразу видны во всех зависимых крейтах.
- Сквозная интеграция — можно легко запускать тесты и линтинг для всего проекта.
- Централизованная документация — все документы и CI/CD находятся в одном месте.
Однако монорепозиторий подходит не всем. Для маленьких проектов или команд с разными циклами разработки лучше выбрать мультирепо. Но для экосистемы Rust, где модульность и переиспользование кода — норма, монорепозиторий становится мощным инструментом.
Организация монорепозитория с Cargo Workspace
Cargo поддерживает механизм workspace — это группа крейтов, которые собираются вместе и используют общий Cargo.lock. Рассмотрим базовую структуру.
Создание workspace
Создайте корневой каталог и файл Cargo.toml без секции [package], а с секцией [workspace]:
# корневой Cargo.toml
[workspace]
members = [
"core",
"cli",
"web",
]
Теперь создайте подкаталоги core, cli, web. В каждом из них должен быть свой Cargo.toml с типичной структурой пакета. Например, для core:
# core/Cargo.toml
[package]
name = "my_core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
Для cli и web можно указать зависимость от my_core:
# cli/Cargo.toml
[package]
name = "my_cli"
version = "0.1.0"
edition = "2021"
[dependencies]
my_core = { path = "../core" }
clap = { version = "4.0", features = ["derive"] }
Теперь при запуске cargo build из корня соберутся все крейты, а Cargo.lock будет общим.
Структура папок и модулей
Рекомендуемая структура монорепозитория для Rust:
my_monorepo/
├── Cargo.toml # workspace
├── Cargo.lock
├── core/ # библиотека с общими типами
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── cli/ # консольное приложение
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
├── web/ # веб-сервер (например, на Actix)
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
├── tests/ # интеграционные тесты (опционально)
│ └── integration_test.rs
├── scripts/ # скрипты для CI/CD
└── README.md
Внутри каждого крейта используйте стандартную модульную структуру Rust. Например, в core/src/lib.rs:
// core/src/lib.rs
pub mod models;
pub mod utils;
А в core/src/models.rs:
// core/src/models.rs
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub id: u64,
pub name: String,
pub email: String,
}
Такая организация позволяет легко переиспользовать User в других крейтах.
Управление публичными зависимостями и версиями
Одна из главных задач в монорепозитории — избежать дублирования зависимостей. Cargo workspace решает это автоматически: все крейты используют один Cargo.lock. Но есть нюансы.
Общие зависимости
Если несколько крейтов используют одну и ту же библиотеку (например, serde), Cargo подтянет её один раз. Однако если крейты требуют разные версии, возникнет конфликт. Чтобы этого избежать, централизуйте зависимости в корневом Cargo.toml с помощью секции [workspace.dependencies] (доступно с Rust 1.64+):
# корневой Cargo.toml
[workspace]
members = ["core", "cli", "web"]
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
Теперь в Cargo.toml каждого крейта можно ссылаться на эти зависимости без указания версии:
# cli/Cargo.toml
[dependencies]
my_core = { path = "../core" }
serde.workspace = true
tokio.workspace = true
Это гарантирует, что все крейты используют одну и ту же версию.
Публичные vs приватные зависимости
В монорепозитории часто выделяют публичные крейты (которые публикуются в crates.io) и приватные (только для внутреннего использования). Чтобы явно указать, что крейт не должен публиковаться, добавьте в его Cargo.toml:
[package] publish = falseБлоги
Рекомендаций по книгам
Читайте также