Введение: почему локальный крейт — это не роскошь, а необходимость
Представьте: вы пишете на Rust библиотеку для парсинга конфигурационных файлов. Она ещё сырая, не готова к публикации на crates.io, но вы уже хотите протестировать её в реальном проекте — веб-сервере на Actix или CLI-утилите. Что делать? Копировать код? Создавать монорепозиторий? Нет, в Rust для этого есть элегантное решение — локальный unpublished crate.
Вопрос "How to use a local unpublished crate?" собрал тысячи просмотров на Stack Overflow. Это не случайно: каждый Rust-разработчик рано или поздно сталкивается с необходимостью подключить свой же крейт, который ещё не вышел в свет. В этой статье мы разберём все способы — от простого указания пути до работы с Git-зависимостями и workspaces. Поехали!
Что такое unpublished crate и зачем он нужен?
Unpublished crate (неопубликованный крейт) — это Rust-пакет, который существует только на вашем локальном диске или в приватном репозитории. Он не загружен на crates.io и не доступен другим разработчикам (если только вы не дадите им доступ к файлам).
Типичные сценарии использования:
- Разработка библиотеки параллельно с основным проектом
- Рефакторинг: вы выделяете часть кода в отдельный крейт
- Приватные корпоративные библиотеки, которые нельзя публиковать
- Тестирование зависимостей перед публикацией
Главное преимущество — вы получаете полный контроль над кодом и можете мгновенно вносить изменения, не ожидая публикации на crates.io.
Способ 1: Path dependency — самый простой и быстрый
Это базовый метод, который подходит, когда оба проекта (зависимость и зависимый) находятся на вашем компьютере.
Структура проекта
Допустим, у вас есть следующая структура папок:
my_project/
├── my_lib/ # unpublished crate
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
└── my_app/ # основной проект
├── Cargo.toml
└── src/
└── main.rs
Шаг 1: Создаём библиотеку my_lib
Внутри my_lib/Cargo.toml:
[package]
name = "my_lib"
version = "0.1.0"
edition = "2021"
[dependencies]
В my_lib/src/lib.rs напишем простую функцию:
pub fn greet(name: &str) -> String {
format!("Привет, {}! Это локальный крейт.", name)
}
Шаг 2: Подключаем my_lib в my_app через path
В my_app/Cargo.toml добавляем секцию [dependencies]:
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
[dependencies]
my_lib = { path = "../my_lib" }
Обратите внимание: путь указывается относительно файла Cargo.toml основного проекта. Если библиотека лежит в другой директории, укажите абсолютный или относительный путь.
Шаг 3: Используем крейт в коде
В my_app/src/main.rs:
use my_lib::greet;
fn main() {
let message = greet("Алексей");
println!("{}", message);
}
Запускаем: cargo run — и видим вывод: "Привет, Алексей! Это локальный крейт."
Важно! Любые изменения в my_lib будут автоматически подхватываться при следующей сборке my_app. Не нужно ничего переустанавливать или обновлять версии.
Способ 2: Git-зависимости — для командной работы
Если вы работаете в команде или храните код в Git, лучше использовать Git-зависимости. Это удобно, когда крейт лежит в отдельном репозитории (даже приватном).
Настройка
Предположим, ваш unpublished крейт my_lib находится в репозитории https://github.com/yourname/my_lib.git. В my_app/Cargo.toml пишем:
[dependencies]
my_lib = { git = "https://github.com/yourname/my_lib.git", branch = "main" }
Можно указать конкретный коммит или тег:
my_lib = { git = "https://github.com/yourname/my_lib.git", rev = "a1b2c3d4" }
my_lib = { git = "https://github.com/yourname/my_lib.git", tag = "v0.1.0-alpha" }
Cargo клонирует репозиторий в кеш и использует его как зависимость. При каждом обновлении (cargo update) он будет проверять новые коммиты.
Для приватных репозиториев используйте SSH:
my_lib = { git = "git@github.com:yourname/my_lib.git" }
Не забудьте настроить SSH-ключи на своей машине!
Способ 3: Workspace — когда проекты живут вместе
Если вы разрабатываете несколько взаимосвязанных крейтов (например, библиотеку и несколько приложений, которые её используют), лучше организовать workspace. Это стандартный подход в крупных Rust-проектах.
Создаём workspace
В корневой директории создаём Cargo.toml (без секции [package]):
[workspace]
members = [
"my_lib",
"my_app",
]
Структура будет такой:
my_workspace/
├── Cargo.toml # workspace root
├── my_lib/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
└── my_app/
├── Cargo.toml
└── src/
└── main.rs
Внутри my_app/Cargo.toml зависимость указываем как обычно через path, но теперь путь короче:
[dependencies]
my_lib = { path = "../my_lib" }
Теперь команда cargo build из корня workspace соберёт сразу оба крейта. Workspace гарантирует, что все зависимости будут разрешены единообразно, и вы можете запускать тесты для всей пачки одним махом: cargo test --workspace.
Частые ошибки и как их избежать
- Ошибка: "package `my_lib` is not a workspace member" — если вы используете workspace, но забыли