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 Workspace

ในบทที่ 12 เราสร้าง package ที่รวม binary crate และ library crate เมื่อโปรเจกต์ของคุณพัฒนา คุณอาจพบว่า library crate ใหญ่ขึ้นต่อเนื่อง และคุณต้องการแยก package ของคุณเพิ่มเข้า library crate หลายตัว Cargo เสนอฟีเจอร์ที่เรียกว่า workspace ที่ช่วยจัดการ package ที่เกี่ยวข้อง หลายตัวที่ถูกพัฒนาควบคู่กัน

สร้าง Workspace

workspace คือชุดของ package ที่แชร์ Cargo.lock และ output directory เดียวกัน มาสร้างโปรเจกต์โดยใช้ workspace — เราจะใช้โค้ดเล็ก น้อยเพื่อให้เราโฟกัสที่โครงสร้างของ workspace มีหลายวิธีในการจัด โครงสร้าง workspace ดังนั้นเราจะแสดงเพียงวิธีทั่วไปหนึ่ง เราจะมี workspace ที่บรรจุ binary และสอง library โดย binary ซึ่งจะให้ functionality หลัก จะขึ้นกับสอง library นั้น library หนึ่งจะให้ฟังก์ชัน add_one และ library อื่นฟังก์ชัน add_two สาม crate เหล่านี้จะเป็นส่วนของ workspace เดียวกัน เราจะเริ่มโดยสร้าง directory ใหม่สำหรับ workspace:

$ mkdir add
$ cd add

ถัดไป ใน directory add เราสร้างไฟล์ Cargo.toml ที่จะ configure workspace ทั้งหมด ไฟล์นี้จะไม่มี section [package] แทน มันจะเริ่ม ด้วย section [workspace] ที่จะอนุญาตให้เราเพิ่ม member ให้ workspace เรายังพยายามใช้ version ล่าสุดและดีที่สุดของอัลกอริทึม resolver ของ Cargo ใน workspace ของเราโดยตั้งค่า resolver เป็น "3":

Filename: Cargo.toml

[workspace]
resolver = "3"

ถัดไป เราจะสร้าง binary crate adder โดยรัน cargo new ภายใน directory add:

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

การรัน cargo new ภายใน workspace ยังเพิ่ม package ที่เพิ่งสร้าง อัตโนมัติให้ key members ในนิยาม [workspace] ใน Cargo.toml ของ workspace แบบนี้:

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

ในจุดนี้ เรา build workspace ได้โดยรัน cargo build ไฟล์ใน directory add ของคุณควรดูแบบนี้:

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

workspace มี directory target หนึ่งที่ระดับสูงที่ artifact ที่ คอมไพล์จะถูกวาง — package adder ไม่มี directory target ของตัวเอง แม้เราจะรัน cargo build จากภายใน directory adder artifact ที่ คอมไพล์จะยังลงเอยที่ add/target ไม่ใช่ add/adder/target Cargo จัด โครงสร้าง directory target ใน workspace แบบนี้เพราะ crate ใน workspace มีไว้ขึ้นต่อกัน ถ้าแต่ละ crate มี directory target ของ ตัวเอง แต่ละ crate จะต้อง recompile แต่ละ crate อื่นใน workspace เพื่อ วาง artifact ใน directory target ของตัวเอง โดยแชร์ directory target เดียว crate หลีกเลี่ยงการ rebuild ที่ไม่จำเป็นได้

สร้าง Package ที่สองใน Workspace

ถัดไป มาสร้าง package member อีกตัวใน workspace และเรียกมัน add_one สร้าง library crate ใหม่ชื่อ 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 ระดับสูงตอนนี้จะรวม path add_one ใน list members:

Filename: Cargo.toml

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

directory add ของคุณตอนนี้ควรมี directory และไฟล์เหล่านี้:

├── 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
}

ตอนนี้เรามี package adder กับ binary ของเราขึ้นกับ package add_one ที่มี library ของเรา ก่อนอื่น เราจะต้องเพิ่ม path dependency บน add_one ให้ adder/Cargo.toml

Filename: adder/Cargo.toml

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

Cargo ไม่สมมติว่า crate ใน workspace จะขึ้นต่อกัน ดังนั้นเราต้อง ชัดเจนเกี่ยวกับความสัมพันธ์ของ dependency

ถัดไป มาใช้ฟังก์ชัน add_one (จาก crate add_one) ใน crate adder เปิดไฟล์ adder/src/main.rs และเปลี่ยนฟังก์ชัน main ให้ เรียกฟังก์ชัน add_one ดังใน Listing 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: ใช้ library crate add_one จาก crate adder

มา build workspace โดยรัน cargo build ใน directory 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

เพื่อรัน binary crate จาก directory add เราระบุว่า package ไหนใน workspace ที่เราต้องการรันโดยใช้อาร์กิวเมนต์ -p และชื่อ package กับ 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 ซึ่งขึ้นกับ crate add_one

ขึ้นกับ Package ภายนอก

สังเกตว่า workspace มีไฟล์ Cargo.lock เพียงไฟล์เดียวที่ระดับสูง ไม่ใช่มี Cargo.lock ใน directory ของแต่ละ crate นี่รับประกันว่า crate ทั้งหมดใช้ version เดียวกันของ dependency ทั้งหมด ถ้าเราเพิ่ม package rand ในไฟล์ adder/Cargo.toml และ add_one/Cargo.toml Cargo จะ resolve ทั้งสองเป็น version หนึ่งของ rand และบันทึกใน Cargo.lock เดียว การทำให้ crate ทั้งหมดใน workspace ใช้ dependency เดียวกันแปลว่า crate จะเข้ากันได้กับกันและกันเสมอ มาเพิ่ม crate rand ที่ section [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 ได้ และ build workspace ทั้งหมดโดยรัน cargo build ใน directory add จะ นำมาและคอมไพล์ crate rand เราจะได้ warning หนึ่งเพราะเราไม่ได้อ้าง ถึง rand ที่เรานำเข้า scope:

$ 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 ระดับสูงตอนนี้บรรจุข้อมูลเกี่ยวกับ dependency ของ add_one บน rand อย่างไรก็ตาม แม้ rand ถูกใช้ที่ใดที่หนึ่งใน workspace เราใช้มันใน crate อื่นใน workspace ไม่ได้ยกเว้นเราเพิ่ม rand ในไฟล์ Cargo.toml ของพวกมันด้วย ตัวอย่างเช่น ถ้าเราเพิ่ม use rand; ในไฟล์ adder/src/main.rs สำหรับ package adder เรา จะได้ error:

$ 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 สำหรับ package adder และระบุว่า rand เป็น dependency สำหรับมันด้วย การ build package adder จะ เพิ่ม rand ใน list ของ dependency สำหรับ adder ใน Cargo.lock แต่ไม่มี copy เพิ่มของ rand ที่จะถูก download Cargo จะรับประกันว่า ทุก crate ในทุก package ใน workspace ที่ใช้ package rand จะใช้ version เดียวกันตราบใดที่พวกมันระบุ version ที่เข้ากันได้ของ rand ประหยัดพื้นที่ของเราและรับประกันว่า crate ใน workspace จะเข้ากันได้ กับกันและกัน

ถ้า crate ใน workspace ระบุ version ที่ไม่เข้ากันได้ของ dependency เดียวกัน Cargo จะ resolve แต่ละตัว แต่ยังพยายาม resolve version น้อยที่สุดเท่าที่จะเป็นไปได้

เพิ่มเทสให้ 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 ใน directory 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

section แรกของ output แสดงว่าเทส it_works ใน crate add_one ผ่าน section ถัดไปแสดงว่าพบเทสศูนย์ใน crate adder และ section สุดท้าย แสดงว่าพบ documentation test ศูนย์ใน crate add_one

เรายังรันเทสสำหรับ crate ตัวใดตัวหนึ่งใน workspace จาก directory ระดับสูงได้โดยใช้ flag -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

output นี้แสดงว่า cargo test รันเฉพาะเทสสำหรับ crate add_one และไม่ได้รันเทส crate adder

ถ้าคุณ publish crate ใน workspace ไปยัง crates.io แต่ละ crate ใน workspace จะต้องถูก publish แยก เช่นเดียวกับ cargo test เรา publish crate ตัวใดตัวหนึ่งใน workspace ของเราได้โดยใช้ flag -p และระบุชื่อของ crate ที่เราต้องการ publish

สำหรับการฝึกเพิ่ม เพิ่ม crate add_two ให้ workspace นี้ในแบบ คล้ายกับ crate add_one!

เมื่อโปรเจกต์ของคุณเติบโต พิจารณาใช้ workspace — มันช่วยให้คุณทำงาน กับ component เล็กลง เข้าใจง่ายขึ้น มากกว่าหนึ่ง blob ใหญ่ของโค้ด นอกจากนี้ การเก็บ crate ใน workspace ทำให้การประสานงานระหว่าง crate ง่ายขึ้นได้ถ้าพวกมันมักถูกเปลี่ยนพร้อมกัน