Aplikasi yang tumbuh sehat jarang berhenti pada satu package. Logika inti dipisah menjadi library sendiri, alat baris perintah menjadi binary sendiri, dan muncul pula crate pendukung untuk laporan atau pengujian — masing-masing dengan Cargo.toml sendiri. Cara naif adalah membuat banyak folder proyek terpisah, tetapi cara tersebut membayar tiga tagihan berulang: folder target tumbuh di tiap proyek sehingga kompilasi dependency sama diulang-ulang, Cargo.lock berbeda-beda sehingga versi crate bisa saling tak cocok, dan mengelola versi antar-crate menjadi kerja manual.

Tagihan pertama paling terasa pada waktu: rand beserta enam pendukungnya memakan waktu kompilasi yang sama di setiap folder proyek yang memakainya — pekerjaan yang hasilnya identik dikerjakan berkali-kali. Tagihan kedua muncul sebagai galat yang hanya terjadi di sebagian mesin: dua lock berbeda memilih dua versi rand berbeda, dan kode yang jalan di satu proyek gagal di proyek lain. Tagihan ketiga menagih saat rilis: menaikkan versi library dan aplikasi secara serempak berarti menyunting banyak manifest satu per satu.

Cargo menawarkan struktur workspace: payung administratif yang menyatukan banyak package berbagi satu lock, satu folder target, dan satu sesi resolusi dependency. Chapter ini menyusun workspace dua anggota untuk kantor konversi satuan, membuktikan pembagian target dan lock dari hasil build nyata, lalu melihat pola workspace.dependencies untuk menyatukan requirement versi.

Anatomi Workspace #

Workspace kita bernama kantor dengan dua anggota: konversi-satuan sebagai library dan kantor-cli sebagai binary pemakainya. Struktur foldernya:

text
kantor/
├── Cargo.toml
├── Cargo.lock
├── target/
├── konversi-satuan/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
└── kantor-cli/
    ├── Cargo.toml
    └── src/
        └── main.rs

Manifest di akar workspace disebut virtual manifest — ia tidak memiliki tabel [package] dan tidak menghasilkan crate sendiri:

toml
[workspace]
resolver = "3"
members = ["konversi-satuan", "kantor-cli"]

Bedah manifest tersebut:

  1. Baris 1 ([workspace]): membuka tabel workspace. Ketiadaan tabel [package] menandakan manifest virtual — akar workspace hanya pengatur, bukan crate.
  2. Baris 2 (resolver = "3"): memilih versi resolver dependency. Cargo versi baru menyarankan nilai "3" untuk edition 2024; nilai ini menentukan cara fitur dan versi disatukan antar anggota.
  3. Baris 3 (members = ["konversi-satuan", "kantor-cli"]): daftar folder anggota workspace, ditulis relatif terhadap lokasi manifest ini. Anggota bisa ditambah kapan saja dengan menambahkan path.

Manifest kedua anggota tetap berbentuk package biasa. Perhatikan manifest kantor-cli:

toml
[package]
name = "kantor-cli"
version = "0.1.0"
edition = "2024"

[dependencies]
konversi-satuan = { path = "../konversi-satuan" }

Dependency antar anggota memakai kunci path — alamat folder saudara, bukan nama registry. Cargo mengetahui kedua package berada dalam satu workspace, sehingga perubahan kode konversi-satuan langsung terlihat oleh kantor-cli pada build berikutnya tanpa langkah publikasi.

Isi src/lib.rs milik konversi-satuan:

rust
pub fn celsius_ke_fahrenheit(celsius: f64) -> f64 {
    celsius * 9.0 / 5.0 + 32.0
}

pub fn km_ke_mil(km: f64) -> f64 {
    km / 1.609
}

Isi src/main.rs milik kantor-cli:

rust
use konversi_satuan::{celsius_ke_fahrenheit, km_ke_mil};

fn main() {
    println!("30 derajat Celsius = {:.1} derajat Fahrenheit", celsius_ke_fahrenheit(30.0));
    println!("42 km = {:.2} mil", km_ke_mil(42.0));
}

Membangun dan Menjalankan Anggota #

Dari folder akar kantor, perintah build mengompilasi seluruh anggota:

bash
cargo build

Output yang Dihasilkan:

text
   Compiling konversi-satuan v0.1.0 (/tmp/w5a/kantor/konversi-satuan)
   Compiling kantor-cli v0.1.0 (/tmp/w5a/kantor/kantor-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s

Urutan kompilasi mengikuti ketergantungan: library dikerjakan lebih dulu, binary menyusul. Untuk menjalankan satu anggota tertentu, opsi -p memilih paketnya:

bash
cargo run -p kantor-cli

Output yang Dihasilkan:

text
30 derajat Celsius = 86.0 derajat Fahrenheit
42 km = 26.10 mil

Opsi -p kantor-cli berarti “kerjakan perintah ini pada package bernama kantor-cli”. Pilihan ini penting karena workspace punya lebih dari satu anggota — mencoba cargo run -p konversi-satuan ditolak dengan pesan error: a bin target must be available for cargo run`` sebab package tersebut library tanpa binary. Bila kita cd ke folder anggota, perintah cargo otomatis menemukan package terdekat, sehingga cargo run di dalam kantor-cli/ langsung menjalankan binary-nya tanpa opsi. Dari akar, perintah lintas anggota lain mengikuti pola yang sama: cargo test -p konversi-satuan hanya menguji library, cargo build -p kantor-cli --release hanya membangun binary dengan profil release.

Perintah cargo build dari akar tanpa -p membangun semua anggota sekaligus — kebiasaan yang nyaman sebelum commit. Sebaliknya, memilih anggota lewat -p menghemat waktu ketika satu anggota sedang intens dikembangkan; kompilasi anggota lain dan dependency mereka tidak diulang karena hasilnya masih tersimpan di target bersama.

Target dan Lock yang Dibagikan #

Janji utama workspace terlihat setelah build. Seluruh artifak berada di kantor/target/debug/ milik akar:

text
target/debug/kantor-cli
target/debug/libkonversi_satuan.rlib

Sementara itu folder kedua anggota hanya berisi Cargo.toml dan src — tidak ada target lokal. Artifak bersama berarti dependency yang dipakai lebih dari satu anggota dikompilasi sekali saja; saat dua anggota sama-sama memasang rand, resolusi dan kompilasi rand hanya terjadi satu kali di satu tempat. Pembagian ini berlaku untuk semua profil sekaligus — target/debug dan target/release berdiri berdampingan di akar dengan isi tiap anggota — serta untuk hasil cargo doc dan cargo test yang juga menumpang folder target yang sama.

Menambah anggota baru pun tidak mengganggu yang lama: buat folder package baru di bawah akar, lalu tambahkan namanya ke daftar members. Build berikutnya otomatis mengikutsertakannya, dan dependency antar anggota cukup diarahkan dengan path seperti contoh kantor-cli di atas.

Berkas Cargo.lock juga hanya ada di akar, merekam kedua anggota sekaligus:

text
[[package]]
name = "kantor-cli"
version = "0.1.0"
dependencies = [
 "konversi-satuan",
]

[[package]]
name = "konversi-satuan"
version = "0.1.0"

Satu lock untuk semua anggota berarti resolusi versi terjadi sekali — dua anggota tidak mungkin terkunci pada versi rand yang berbeda. Perintah cargo tree dari akar menggambarkan seluruh pohon sekaligus:

text
kantor-cli v0.1.0 (/tmp/w5a/kantor/kantor-cli)
└── konversi-satuan v0.1.0 (/tmp/w5a/kantor/konversi-satuan)

konversi-satuan v0.1.0 (/tmp/w5a/kantor/konversi-satuan)

Relasi workspace dan anggotanya dapat diringkas sebagai berikut:

Diagram tersebut menegaskan pembagian peran: virtual manifest sebagai payung, kedua anggota sebagai crate mandiri, dan dependency antar anggota tetap dinyatakan eksplisit lewat path.

Menyatukan Requirement dengan workspace.dependencies #

Ketika beberapa anggota memakai crate eksternal yang sama, requirement versinya sebaiknya ditulis satu kali di akar:

toml
[workspace]
resolver = "3"
members = ["konversi-satuan", "kantor-cli"]

[workspace.dependencies]
rand = "0.10"

Anggota cukup mewarisi requirement tersebut dengan sintaks rand.workspace = true pada tabel [dependencies]-nya — satu baris tanpa nomor versi. Build nyata pada struktur seperti ini menghasilkan cargo tree berikut:

text
kantor-cli v0.1.0 (/tmp/w5a/kantor-wsdep/kantor-cli)
└── konversi-satuan v0.1.0 (/tmp/w5a/kantor-wsdep/konversi-satuan)
    └── rand v0.10.3
        ├── chacha20 v0.10.2
        │   ├── cfg-if v1.0.5
        │   ├── cpufeatures v0.3.1
        │   └── rand_core v0.10.1
        ├── getrandom v0.4.3

Pohon tersebut menunjukkan rand beserta seluruh dependency pendukungnya berada di bawah konversi-satuan — anggota yang memintanya. Satu sumber requirement di akar mencegah dua anggota menulis rentang rand yang berbeda dan saling berkonflik saat resolusi.

Ringkasan #

Workspace menyatukan banyak package di bawah satu virtual manifest [workspace] berisi daftar members: dependency antar anggota memakai kunci path, perintah cargo build dari akar mengompilasi semua anggota berurutan sesuai ketergantungan, dan cargo run -p <nama> memilih anggota yang dijalankan. Satu Cargo.lock dan satu folder target di akar dibagi bersama sehingga versi konsisten dan kompilasi tidak diulang, sementara [workspace.dependencies] menyatukan requirement crate eksternal. Manfaat pembagian ini paling terasa ketika proses build perlu langkah khusus — menghasilkan berkas, membaca konfigurasi lingkungan — yang kita bahas lewat build script pada Chapter berikutnya.