Kita kini menguasai seluruh perkakas organisasi: mod, path, use, pub, re-export, hingga pemisahan berkas. Perkakas itu menunggu satu hal terakhir — keputusan arsitektur. Keputusan paling berpengaruh pada proyek Rust berukuran apa pun adalah memisahkan library dari binary: seluruh logika bisnis hidup di crate library yang bisa diuji dan dipakai ulang, sementara binary menipis menjadi pembaca argumen dan pemanggil library. Chapter ini menyusun proyek warung lengkap — module utama publik, module util internal, dan binary minimal — mengikuti konvensi yang dipakai komunitas Rust dan ribuan crate di crates.io.
Masalah: main.rs yang Menjadi Gudang #
Menaruh seluruh module di bawah src/main.rs memang sah, namun biayanya muncul saat proyek dewasa. Pertama, seluruh kode hanya bisa dijalankan sebagai program — tidak ada cara memakainya sebagai library, misalnya dari tool lain atau dari test terpisah yang mengimpor crate kita. Kedua, main.rs terpaksa merangkap dua peran sekaligus: titik masuk program dan crate root seluruh logika. Peran campuran itu membuat argumen CLI, logika bisnis, dan utilitas jadi berdesakan dalam satu berkas. Ketiga, pengujian menyulit: unit test terbaik menempel dekat fungsinya, sedangkan integrasi test pada folder tests/ hanya bisa mengimpor crate yang punya target library.
Solusi idiomatiknya satu kalimat: letakkan logika di src/lib.rs, biarkan src/main.rs setipis mungkin. Kedua berkas itu tetap satu package — satu Cargo.toml, satu folder target — namun membentuk dua crate: crate library bernama warung dan crate binary yang kebetulan juga bernama warung.
Package dengan Dua Crate #
Mekanisme di balik pemisahan itu perlu dipahami presisi. Sebuah package adalah unit manajemen Cargo: folder berisi Cargo.toml yang menyimpan nama, versi, dan dependensi. Package boleh memuat beberapa target — dan dua target istimewa dibaca langsung dari lokasi berkasnya: src/lib.rs otomatis menjadi target library, src/main.rs otomatis menjadi target binary. Nama library mengikuti nama package, sehingga package warung menghasilkan crate library bernama warung yang diimpor lewat use warung::....
Karena binary dan library adalah dua crate berbeda, binary memperlakukan library kita seperti crate luar: seluruh aturan visibility publik berlaku penuh — item tanpa pub di library tidak akan terlihat dari main.rs. Justru pemisahan inilah yang menuntut disiplin antarmuka: module util internal cukup dideklarasikan mod utils; tanpa pub, dan otomatis tersembunyi dari binary.
Kedua target itu dibangun dari satu perintah yang sama. cargo run mengompilasi kedua crate lalu menjalankan binary; cargo build mengompilasi keduanya tanpa menjalankan; dan cargo test mengompilasi library beserta seluruh testnya. Pemisahan ini memungkinkan pemakai lain memakai logika warung tanpa menjalankan program — cukup menambahkan warung sebagai dependensi di Cargo.toml mereka, dan seluruh antarmuka publik langsung tersedia lewat use warung::.... Sebuah package juga bisa menampung binary tambahan di folder src/bin/ bila satu program kurang, tetapi pola dasarnya tetap: satu library, satu binary utama.
Proyek warung dari Nol #
Kita bangun proyeknya. Struktur akhir mengikuti pola yang dipakai mayoritas crate publik:
warung/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── menu.rs
├── kitchen.rs
└── utils.rsCrate root library src/lib.rs mendeklarasikan seluruh module dan satu fungsi sambutan:
pub mod kitchen;
pub mod menu;
mod utils;
pub fn sambut(nama: &str) {
println!("Selamat datang di Warung Kita, {}!", nama);
}Module util internal src/utils.rs tidak diekspor — hanya warga crate library yang boleh memakainya:
pub fn format_rupiah(nilai: u32) -> String {
format!("Rp{}", nilai)
}Module utama src/menu.rs menyimpan tipe dan fungsi domain:
use crate::utils::format_rupiah;
pub struct MenuItem {
pub nama: String,
pub harga: u32,
}
pub fn menu_favorit() -> Vec<MenuItem> {
vec![
MenuItem { nama: String::from("Nasi Goreng Spesial"), harga: 25000 },
MenuItem { nama: String::from("Ayam Bakar Madu"), harga: 30000 },
]
}
pub fn tampilkan_menu() {
for item in menu_favorit() {
println!("{} - {}", item.nama, format_rupiah(item.harga));
}
}Module utama src/kitchen.rs menangani proses memasak:
pub fn masak(nama_pesanan: &str) {
println!("Dapur memasak: {}", nama_pesanan);
}Terakhir, binary src/main.rs menipis menjadi orkestrator:
use warung::kitchen;
use warung::menu;
fn main() {
warung::sambut("Budi");
menu::tampilkan_menu();
kitchen::masak("Nasi Goreng Spesial");
}Jalankan seperti biasa:
cargo runOutput yang Dihasilkan:
Selamat datang di Warung Kita, Budi!
Nasi Goreng Spesial - Rp25000
Ayam Bakar Madu - Rp30000
Dapur memasak: Nasi Goreng SpesialMari kita bedah berkas-berkas kunci di atas:
- Baris 1–2 pada lib.rs (
pub mod kitchen; pub mod menu;): dua module domain dideklarasikan publik — keduanya adalah antarmuka resmi cratewarungbagi pemakai luar, termasuk binary kita sendiri. - Baris 4 pada lib.rs (
mod utils;): module util dideklarasikan tanpapub. Deklarasi ini menempatkanutilsdi tree crate library namun tidak mengekspornya — binary dan pemakai eksternal tidak bisa menuliswarung::utils::.... - Baris 1 pada menu.rs (
use crate::utils::format_rupiah;): sesama warga crate library tetap bebas meminjam utilitas internal. Pathcrate::di sini merujuk crate library, karena berkasmenu.rsadalah bagian dari crate tersebut. - Baris 8–13 pada menu.rs (
pub fn menu_favorit()): fungsi penghasil data membangunVec<MenuItem>— struct dengan field publik yang aman dibaca pemakai. - Baris 1–2 pada main.rs (
use warung::kitchen; use warung::menu;): binary mengimpor module library persis seperti mengimpor crate luar. Namawarungpada path itu adalah nama crate library, diambil dari nama package diCargo.toml. - Baris 5–7 pada main.rs: alur program terbaca sebagai cerita — sambut, tampilkan menu, masak pesanan. Tidak ada satu baris logika bisnis di binary; semuanya didelegasikan ke library.
Hubungan kedua crate dalam package tersebut dapat kita petakan:
Diagram menegaskan arah ketergantungan satu arah: binary bergantung pada library, dan hanya menyentuh module yang sengaja dibuka — panah dari main.rs berhenti di menu dan kitchen, tidak pernah menyentuh utils.
Konvensi Komunitas yang Kita Ikuti #
Struktur di atas bukan gaya bebas — ia mengikuti kebiasaan yang stabil di ekosistem Rust. Daftar periksa singkatnya: nama package di Cargo.toml menjadi nama crate library, jadi pilihlah nama yang enak ditulis setelah use; src/lib.rs berfungsi sebagai crate root yang hanya berisi deklarasi mod dan item pendek yang mewakili identitas crate; module util internal dideklarasikan tanpa pub agar otomatis tersembunyi; binary di src/main.rs menipis menjadi orkestrator; dan unit test ditempatkan di dalam module masing-masing dengan atribut #[cfg(test)] — pola yang kita pakai pada Chapter berikutnya.
Ringkasan #
Struktur idiomatik proyek Rust memisahkan dua peran dalam satu package: crate library dari src/lib.rs membawa seluruh module domain dan util, sedangkan crate binary dari src/main.rs menipis menjadi pemanggil library melalui use warung::.... Module util cukup dideklarasikan privat agar otomatis tidak masuk antarmuka, dan arah ketergantungan yang satu arah menjaga antarmuka tetap disiplin. Struktur ini juga menjadi fondasi pengujian: module test dapat menempel pada tiap module di library, sementara integration test di folder tests/ menguji antarmuka publik persis seperti pemakai nyata. Dengan struktur ini, satu lapisan kontrol masih kurang: bagaimana membuka item untuk sesama crate library tanpa membocorkannya ke pemakai luar? Jawabannya adalah pub(crate) — pembahasan Chapter penutup Section ini.