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

В розділі 12, ми зібрали пакет, який включав двійковий крейт та бібліотечний крейт. В міру розвитку вашого проекту, ви можете виявити, що бібліотечний крейт продовжує становитися більшим і вам хочеться розділити ваш пакет на декілька бібліотечних крейтів. Cargo пропонує функціонал названий робочими областями, який може допомогти в керуванні кількома пов'язаними пакетами, які розробляються в тандемі.

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

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

$ mkdir add
$ cd add

Далі, в каталозі add, ми створимо файл Cargo.toml який налаштує всю робочу область. Цей файл не матиме секції [package]. Натомість він розпочнеться з секції [workspace], яка дозволить нам додавати учасників до робочої області, вказавши шлях до пакета із нашим двійковим крейтов; у цьому випадку, цей шлях adder:

Файл: Cargo.toml

[workspace]

members = [
    "adder",
]

Next, we’ll create the adder binary crate by running cargo new within the add directory:

$ cargo new adder
     Created binary (application) `adder` package

Наразі ми можемо зібрати робочу область запустивши 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, крейти можуть уникнути непотрібних повторних збірок.

Створення Другого Пакета в Робочій Області

Далі створимо ще один (member) пакет в робочій області і назвемо його add_one. Змініть Cargo.toml верхнього рівня, вказав шлях до add_one в списку members:

Файл: Cargo.toml

[workspace]

members = [
    "adder",
    "add_one",
]

Потім згенеруйте новий бібліотечний крейт, названий add_one:

$ cargo new add_one --lib
     Created library `add_one` package

Ваш каталог 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:

Файл: add_one/src/lib.rs

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

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

Файл: adder/Cargo.toml

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

Cargo doesn’t assume that crates in a workspace will depend on each other, so we need to be explicit about the dependency relationships.

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

Файл: adder/src/main.rs

use add_one;

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

Listing 14-7: Using the add_one library crate from the adder crate

Let’s build the workspace by running cargo build in the top-level add directory!

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

To run the binary crate from the add directory, we can specify which package in the workspace we want to run by using the -p argument and the package name with cargo run:

$ cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     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:

Файл: add_one/Cargo.toml

[dependencies]
rand = "0.8.3"

Тепер ми можемо додати 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
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 10.18s

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 однакової версії, що збереже нам простір та запевнить, що крейти в робочій області будуть сумісними один з одним.

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

For another enhancement, let’s add a test of the add_one::add_one function within the add_one crate:

Файл: 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 [unoptimized + debuginfo] target(s) in 0.27s
     Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841)

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-49979ff40686fa8e)

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.

We can also run tests for one particular crate in a workspace from the top-level directory by using the -p flag and specifying the name of the crate we want to test:

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

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

This output shows cargo test only ran the tests for the add_one crate and didn’t run the adder crate tests.

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

For additional practice, add an add_two crate to this workspace in a similar way as the add_one crate!

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