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

Workspaces در Cargo

در فصل 12، ما یک پکیج ساختیم که شامل یک crate باینری و یک crate کتابخانه‌ای بود. همان‌طور که پروژه شما توسعه می‌یابد، ممکن است متوجه شوید که crate کتابخانه‌ای همچنان بزرگ‌تر می‌شود و بخواهید پکیج خود را بیشتر به crate‌های کتابخانه‌ای چندگانه تقسیم کنید. Cargo یک ویژگی به نام workspaces ارائه می‌دهد که می‌تواند به مدیریت پکیج‌های مرتبط که به صورت همزمان توسعه داده می‌شوند کمک کند.

Creating a Workspace

A workspace is a set of packages that share the same Cargo.lock and output directory. Let’s make a project using a workspace—we’ll use trivial code so we can concentrate on the structure of the workspace. There are multiple ways to structure a workspace, so we’ll just show one common way. We’ll have a workspace containing a binary and two libraries. The binary, which will provide the main functionality, will depend on the two libraries. One library will provide an add_one function, and a second library an add_two function. These three crates will be part of the same workspace. We’ll start by creating a new directory for the workspace:

$ mkdir add
$ cd add

Next, in the add directory, we create the Cargo.toml file that will configure the entire workspace. This file won’t have a [package] section. Instead, it will start with a [workspace] section that will allow us to add members to the workspace. We also make a point to use the latest and greatest version of Cargo’s resolver algorithm in our workspace by setting the resolver to "2".

Filename: Cargo.toml

[workspace]
resolver = "3"

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

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

Running cargo new inside a workspace also automatically adds the newly created package to the members key in the [workspace] definition in the workspace Cargo.toml, like this:

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

در این مرحله، می‌توانیم workspace را با اجرای دستور cargo build بسازیم. فایل‌های موجود در دایرکتوری add شما باید به این صورت باشند:

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

Workspace یک دایرکتوری target در سطح بالا دارد که فایل‌های کامپایل‌شده در آن قرار خواهند گرفت. پکیج adder دایرکتوری target اختصاصی خود را ندارد. حتی اگر دستور cargo build را از داخل دایرکتوری adder اجرا کنیم، فایل‌های کامپایل‌شده همچنان در add/target قرار می‌گیرند نه در add/adder/target. Cargo دایرکتوری target را در یک workspace به این صورت ساختاردهی می‌کند زیرا crate‌های موجود در یک workspace برای وابستگی به یکدیگر طراحی شده‌اند. اگر هر crate دایرکتوری target اختصاصی خود را داشت، هر crate مجبور بود هر کدام از crate‌های دیگر را در workspace دوباره کامپایل کند تا فایل‌های کامپایل‌شده را در دایرکتوری target خود قرار دهد. با به اشتراک‌گذاری یک دایرکتوری target، crate‌ها می‌توانند از ساخت مجدد غیرضروری جلوگیری کنند.

ایجاد پکیج دوم در Workspace

Next, let’s create another member package in the workspace and call it add_one. Change the top-level Cargo.toml to specify the add_one path in the members list:

Filename: Cargo.toml

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

Then generate a new library crate named add_one:

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

The top-level Cargo.toml will now include the add_one path in the members list:

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 که حاوی کتابخانه ما است کنیم. ابتدا باید یک وابستگی مسیر (path dependency) به add_one در فایل adder/Cargo.toml اضافه کنیم.

Filename: adder/Cargo.toml

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

Cargo فرض نمی‌کند که crate‌های موجود در یک workspace به یکدیگر وابسته هستند، بنابراین ما باید به صراحت روابط وابستگی را مشخص کنیم.

در ادامه، بیایید از تابع add_one (از crate به نام add_one) در crate به نام adder استفاده کنیم. فایل adder/src/main.rs را باز کنید و تابع main را تغییر دهید تا تابع add_one را فراخوانی کند، همان‌طور که در لیست ۱۴-۷ نشان داده شده است.

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: Using the add_one library crate in the adder crate

بیایید workspace را با اجرای دستور 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

برای اجرای crate باینری از دایرکتوری add، می‌توانیم با استفاده از آرگومان -p و نام پکیج همراه با دستور cargo run مشخص کنیم کدام پکیج در workspace اجرا شود:

$ 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 را اجرا می‌کند که به crate add_one وابسته است.

وابستگی به یک پکیج خارجی در یک Workspace

توجه کنید که workspace فقط یک فایل Cargo.lock در سطح بالا دارد، به جای اینکه هر crate دایرکتوری خود فایل Cargo.lock داشته باشد. این اطمینان حاصل می‌کند که تمام crate‌ها از همان نسخه تمام وابستگی‌ها استفاده می‌کنند. اگر پکیج rand را به فایل‌های adder/Cargo.toml و add_one/Cargo.toml اضافه کنیم، Cargo هر دو را به یک نسخه از rand تبدیل می‌کند و آن را در فایل Cargo.lock ثبت می‌کند. اطمینان از اینکه همه crate‌های موجود در workspace از همان وابستگی‌ها استفاده می‌کنند، به این معناست که crate‌ها همیشه با یکدیگر سازگار خواهند بود. بیایید پکیج rand را به بخش [dependencies] در فایل add_one/Cargo.toml اضافه کنیم تا بتوانیم از crate rand در crate add_one استفاده کنیم:

Filename: add_one/Cargo.toml

[dependencies]
rand = "0.8.5"

حالا می‌توانیم use rand; را به فایل add_one/src/lib.rs اضافه کنیم و با اجرای دستور cargo build در دایرکتوری add کل workspace را بسازیم، که crate 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 در جایی از workspace استفاده شود، نمی‌توانیم از آن در crate‌های دیگر workspace استفاده کنیم مگر اینکه 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`

To fix this, edit the Cargo.toml file for the adder package and indicate that rand is a dependency for it as well. Building the adder package will add rand to the list of dependencies for adder in Cargo.lock, but no additional copies of rand will be downloaded. Cargo will ensure that every crate in every package in the workspace using the rand package will be using the same version as long as they specify compatible versions of rand, saving us space and ensuring that the crates in the workspace will be compatible with each other.

If crates in the workspace specify incompatible versions of the same dependency, Cargo will resolve each of them, but will still try to resolve as few versions as possible.

افزودن یک تست به یک Workspace

برای یک بهبود دیگر، بیایید یک تست برای تابع add_one::add_one در crate 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 در یک workspace با ساختاری مانند این، تست‌های تمام crate‌های موجود در workspace را اجرا خواهد کرد:

$ 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 در crate add_one پاس شده است. بخش بعدی نشان می‌دهد که هیچ تستی در crate adder پیدا نشده است، و سپس بخش آخر نشان می‌دهد که هیچ تست مستنداتی در crate add_one پیدا نشده است.

ما همچنین می‌توانیم تست‌های یک crate خاص در workspace را از دایرکتوری سطح بالا با استفاده از گزینه -p و مشخص کردن نام crate‌ای که می‌خواهیم تست کنیم، اجرا کنیم:

$ 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 فقط تست‌های crate add_one را اجرا کرده و تست‌های crate adder را اجرا نکرده است.

If you publish the crates in the workspace to crates.io, each crate in the workspace will need to be published separately. Like cargo test, we can publish a particular crate in our workspace by using the -p flag and specifying the name of the crate we want to publish.

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

As your project grows, consider using a workspace: it’s easier to understand smaller, individual components than one big blob of code. Furthermore, keeping the crates in a workspace can make coordination between crates easier if they are often changed at the same time.