Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Робочі області Cargo

У розділі 12 ми зібрали пакет, який включав бінарний крейт і бібліотечний крейт. У міру розвитку вашого проєкту ви можете виявити, що бібліотечний крейт продовжує зростати, і ви хочете далі розділити ваш пакет на кілька бібліотечних крейтів. Cargo пропонує можливість, яка називається робочі області (workspaces), що може допомогти керувати кількома пов’язаними пакетами, які розробляються одночасно.

Створення робочої області

Робоча область — це набір пакетів, які спільно використовують один і той самий Cargo.lock і каталог виводу. Давайте створимо проєкт із використанням робочої області — ми використаємо тривіальний код, щоб могли зосередитися на структурі робочої області. Є кілька способів структурувати робочу область, тому ми покажемо лише один поширений спосіб. У нас буде робоча область, що міститиме бінарний крейт і два бібліотечні крейти. Бінарний крейт, який надаватиме основну функціональність, залежатиме від двох бібліотек. Одна бібліотека надаватиме функцію add_one, а інша бібліотека — функцію add_two. Ці три крейти будуть частиною однієї й тієї самої робочої області. Почнемо зі створення нового каталогу для робочої області:

$ mkdir add
$ cd add

Далі в каталозі add ми створюємо файл Cargo.toml, який налаштовуватиме всю робочу область. У цьому файлі не буде секції [package]. Натомість він починатиметься з секції [workspace], яка дозволить нам додавати учасників до робочої області. Також ми спеціально використаємо найновіший і найкращий алгоритм розв’язувача Cargo у нашій робочій області, встановивши значення resolver у "3":

Filename: Cargo.toml

[workspace]
resolver = "3"

Далі ми створимо бінарний крейт adder, запустивши cargo new всередині каталогу add:

$ cargo new adder
     Created binary (application) `adder` package
      Adding `adder` as member of workspace at `file:///projects/add`

Запуск cargo new всередині робочої області також автоматично додає щойно створений пакет до ключа members у визначенні [workspace] у кореневому Cargo.toml робочої області, ось так:

[workspace]
resolver = "3"
members = ["adder"]

На цьому етапі ми можемо зібрати робочу область, запустивши cargo build. Файли у вашому каталозі add мають виглядати так:

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Робоча область має один каталог target на верхньому рівні, куди будуть поміщені зібрані артефакти; пакет adder не має власного каталогу target. Навіть якщо ми запустимо cargo build із каталогу adder, зібрані артефакти все одно опиняться в add/target, а не в add/adder/target. Cargo структурує каталог target у робочій області саме так, тому що крейти в робочій області призначені для того, щоб залежати один від одного. Якби кожен крейт мав власний каталог target, кожному крейту довелося б перекомпілювати кожен інший крейт у робочій області, щоб помістити артефакти у свій власний каталог target. Спільне використання одного каталогу target дозволяє крейтам уникати непотрібної повторної збірки.

Створення другого пакета в робочій області

Далі створімо ще один пакет-учасник у робочій області й назвімо його add_one. Згенеруйте новий бібліотечний крейт із назвою add_one:

$ cargo new add_one --lib
     Created library `add_one` package
      Adding `add_one` as member of workspace at `file:///projects/add`

Кореневий Cargo.toml тепер включатиме шлях add_one у список members:

Filename: Cargo.toml

[workspace]
resolver = "3"
members = ["adder", "add_one"]

Тепер ваш каталог add має такі каталоги й файли:

├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

У файлі add_one/src/lib.rs додаймо функцію add_one:

Filename: add_one/src/lib.rs

pub fn add_one(x: i32) -> i32 {
    x + 1
}

Тепер ми можемо зробити так, щоб пакет adder із нашим бінарним крейтом залежав від пакета add_one, у якому є наша бібліотека. Спочатку нам потрібно буде додати залежність за шляхом на add_one до adder/Cargo.toml.

Filename: adder/Cargo.toml

[dependencies]
add_one = { path = "../add_one" }

Cargo не припускає, що крейти в робочій області залежатимуть один від одного, тому нам потрібно явно вказати залежності між ними.

Далі давайте використаємо функцію add_one (з крейту add_one) у крейті adder. Відкрийте файл adder/src/main.rs і змініть функцію main, щоб вона викликала функцію add_one, як у Переліку 14-7.

fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

Давайте збудуємо робочу область, запустивши cargo build у верхньому каталозі add!

$ cargo build
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s

Щоб запустити бінарний крейт із каталогу add, ми можемо вказати, який пакет у робочій області хочемо запустити, використавши аргумент -p і назву пакета з cargo run:

$ cargo run -p adder
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!

Це запускає код у adder/src/main.rs, який залежить від крейту add_one.

Залежність від зовнішнього пакета

Зверніть увагу, що робоча область має лише один файл Cargo.lock на верхньому рівні, а не по одному Cargo.lock у каталозі кожного крейту. Це гарантує, що всі крейти використовують одну й ту саму версію всіх залежностей. Якщо ми додамо пакет rand до файлів adder/Cargo.toml і add_one/Cargo.toml, Cargo розв’яже обидва з них до однієї версії rand і запише це в один Cargo.lock. Використання однакових залежностей для всіх крейтів у робочій області означає, що крейти завжди будуть сумісні один з одним. Додаймо крейт rand до секції [dependencies] у файлі add_one/Cargo.toml, щоб ми могли використовувати крейт rand у крейті add_one:

Filename: add_one/Cargo.toml

[dependencies]
rand = "0.8.5"

Тепер ми можемо додати use rand; до файлу add_one/src/lib.rs, і побудова всієї робочої області шляхом запуску cargo build у каталозі add підтягне й скомпілює крейт rand. Ми отримаємо одне попередження, тому що не звертаємося до rand, який ми ввели в область видимості:

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   --snip--
   Compiling rand v0.8.5
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
 --> add_one/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s

Кореневий Cargo.lock тепер містить інформацію про залежність add_one від rand. Однак, навіть якщо rand використовується десь у робочій області, ми не можемо використовувати його в інших крейтах у робочій області, якщо не додамо rand і до їхніх файлів Cargo.toml. Наприклад, якщо ми додамо use rand; до файлу adder/src/main.rs для пакета adder, ми отримаємо помилку:

$ cargo build
  --snip--
   Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
 --> adder/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`

Щоб виправити це, відредагуйте файл Cargo.toml для пакета adder і вкажіть, що rand є також його залежністю. Збирання пакета adder додасть rand до списку залежностей для adder у Cargo.lock, але жодних додаткових копій rand не буде завантажено. Cargo гарантує, що кожен крейт у кожному пакеті в робочій області, який використовує пакет rand, використовуватиме ту саму версію, доки вони вказують сумісні версії rand, заощаджуючи нам місце й забезпечуючи сумісність крейтів у робочій області один з одним.

Якщо крейти в робочій області вказують несумісні версії однієї й тієї самої залежності, Cargo розв’яже кожну з них, але все одно намагатиметься розв’язати якомога менше версій.

Додавання тесту до робочої області

Для ще одного вдосконалення давайте додамо тест функції add_one::add_one у крейті add_one:

Filename: add_one/src/lib.rs

pub fn add_one(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(3, add_one(2));
    }
}

Тепер запустіть cargo test у верхньому каталозі add. Запуск cargo test у робочій області, структурованій так, як ця, запустить тести для всіх крейтів у робочій області:

$ cargo test
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/adder-3a47283c568d2b6a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests add_one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Перша секція виводу показує, що тест it_works у крейті add_one пройшов. Наступна секція показує, що в крейті adder не було знайдено жодного тесту, а остання секція показує, що в крейті add_one не було знайдено жодного документаційного тесту.

Ми також можемо запускати тести для одного конкретного крейту в робочій області з верхнього каталогу, використовуючи прапорець -p і вказуючи назву крейту, який ми хочемо протестувати:

$ cargo test -p add_one
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests add_one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Цей вивід показує, що cargo test запустив лише тести для крейту add_one і не запускав тести крейту adder.

Якщо ви публікуєте крейти в робочій області на crates.io, кожен крейт у робочій області доведеться публікувати окремо. Як і для cargo test, ми можемо опублікувати певний крейт у нашій робочій області, використовуючи прапорець -p і вказуючи назву крейту, який ми хочемо опублікувати.

Для додаткової практики додайте крейт add_two до цієї робочої області подібним чином, як і крейт add_one!

У міру того як ваш проєкт зростає, розгляньте використання робочої області: вона дає змогу працювати з меншими, легшими для розуміння компонентами, а не з одним великим блоком коду. Крім того, спільне використання крейтів у робочій області може полегшити координацію між крейтами, якщо вони часто змінюються одночасно.