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

Создание рабочего пространства

Рабочее пространство – это набор пакетов, которые совместно используют один и тот же Cargo.lock и каталог вывода. Создадим проект с рабочим пространством: мы будем использовать простой код, чтобы сосредоточиться на структуре рабочего пространства. Есть несколько способов структурировать рабочее пространство, поэтому мы покажем один распространенный способ. У нас будет рабочее пространство с одним бинарным крейтом и двумя библиотеками. Бинарный крейт, который будет предоставлять основную функциональность, будет зависеть от двух библиотек. Одна библиотека будет предоставлять функцию add_one, а другая – функцию add_two. Эти три крейта будут частью одного рабочего пространства. Начнем с создания нового каталога для рабочего пространства:

$ mkdir add
$ cd add

Далее в каталоге add мы создаем файл Cargo.toml, который будет настраивать все рабочее пространство. В этом файле не будет раздела [package]. Вместо этого он начнется с раздела [workspace], который позволит нам добавлять участников в рабочее пространство. Мы также явно используем самую новую и лучшую версию алгоритма разрешения зависимостей Cargo в нашем рабочем пространстве, установив значение resolver в "3":

Имя файла: 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:

Имя файла: 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:

Имя файла: 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 не предполагает, что крейты в рабочем пространстве будут зависеть друг от друга, поэтому нам нужно явно указать отношения зависимостей.

Далее используем функцию add_one (из крейта add_one) в крейте adder. Откройте файл adder/src/main.rs и измените функцию main, чтобы она вызывала функцию add_one, как в листинге 14-7.

Filename: adder/src/main.rs
fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
Listing 14-7: Использование библиотечного крейта add_one из крейта adder

Соберем рабочее пространство, выполнив 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:

Имя файла: 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:

Имя файла: 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!

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