Paths (Jalur) buat Ngerujuk Item di Pohon Modul
Buat ngasih tau Rust di mana harus nyari sebuah item di pohon modul, kita pake path (jalur) dengan cara yang sama kayak kita pake path pas navigasi sistem file. Buat manggil sebuah fungsi, kita harus tau path-nya.
Sebuah path bisa punya dua bentuk:
- Absolute path (path absolut) adalah path lengkap mulai dari crate root;
buat kode dari crate eksternal, absolute path dimulai pake nama crate-nya,
dan buat kode dari crate saat ini, dia dimulai pake literal
crate. - Relative path (path relatif) dimulai dari modul saat ini terus pake
self,super, atau identifier (nama) di modul saat ini.
Baik absolute maupun relative path diikuti sama satu atau lebih identifier yang
dipisahin pake titik dua ganda (::).
Balik lagi ke Listing 7-1, katakanlah kita mau manggil fungsi add_to_waitlist.
Ini sama aja kayak nanya: apa sih path dari fungsi add_to_waitlist?
Listing 7-3 isinya Listing 7-1 tapi beberapa modul sama fungsinya dihapus biar
fokus.
Kita bakal nunjukin dua cara buat manggil fungsi add_to_waitlist dari fungsi
baru, eat_at_restaurant, yang didefinisikan di crate root. Path-path ini
udah bener, tapi ada satu masalah lagi yang bakal nyegah contoh ini buat bisa
di-compile gitu aja. Kita bakal jelasin alasannya bentar lagi.
Fungsi eat_at_restaurant itu bagian dari API public dari library crate
kita, jadi kita nandain dia pake keyword pub. Di bagian “Mengekspos Paths
dengan Keyword pub”, kita bakal bahas pub lebih detail.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist pake absolute dan relative pathsPertama kali kita manggil fungsi add_to_waitlist di eat_at_restaurant,
kita pake absolute path. Fungsi add_to_waitlist didefinisikan di crate yang
sama kayak eat_at_restaurant, yang artinya kita bisa pake keyword crate
buat mulai absolute path-nya. Terus kita masukin tiap modul secara berurutan
sampe kita nyampe ke add_to_waitlist. Bayangin aja sistem file dengan
struktur yang sama: kita bakal nentuin path
/front_of_house/hosting/add_to_waitlist buat jalanin program add_to_waitlist;
pake nama crate buat mulai dari crate root itu kayak pake / buat mulai
dari root sistem file di terminal (shell) kita.
Kedua kalinya kita manggil add_to_waitlist di eat_at_restaurant, kita
pake relative path. Path-nya dimulai dari front_of_house, nama modul yang
didefinisikan di level yang sama di pohon modul dengan eat_at_restaurant. Di
sini, kalau di sistem file, ini sama aja kayak pake path
front_of_house/hosting/add_to_waitlist. Mulai pake nama modul artinya
path-nya itu relatif.
Milih buat pake relative atau absolute path itu keputusan yang bakal kita ambil
berdasarkan project kita, dan itu tergantung apakah kita lebih sering mindahin
kode definisi item secara terpisah atau barengan sama kode yang pake item itu.
Misalnya, kalau kita mindahin modul front_of_house sama fungsi
eat_at_restaurant ke dalem modul namanya customer_experience, kita harus
update absolute path ke add_to_waitlist, tapi relative path-nya tetep bakal
valid. Sebaliknya, kalau kita mindahin fungsi eat_at_restaurant secara
terpisah ke dalem modul namanya dining, absolute path buat manggil
add_to_waitlist bakal tetep sama, tapi relative path-nya harus di-update.
Preferensi kita secara umum adalah nentuin pake absolute path karena biasanya
kita lebih sering mindahin definisi kode sama pemanggilan item secara
independen satu sama lain.
Yuk kita coba compile Listing 7-3 dan cari tau kenapa ini belum bisa di-compile! Error yang kita dapet ditunjukin di Listing 7-4.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Pesan error-nya bilang kalau modul hosting itu private. Dengan kata lain,
kita udah punya path yang bener buat modul hosting sama fungsi
add_to_waitlist, tapi Rust nggak ngebolehin kita pake mereka karena Rust
nggak punya akses ke bagian private-nya. Di Rust, semua item (fungsi, method,
struct, enum, modul, sama konstanta) itu private terhadap modul induknya
secara default. Kalau kita mau bikin sebuah item kayak fungsi atau struct jadi
private, kita taruh dia di dalem modul.
Item di modul induk nggak bisa pake item private di dalem anak modulnya (child modules), tapi item di anak modul bisa pake item di modul leluhurnya (ancestor modules). Ini karena anak modul ngebungkus dan nyembunyiin detail implementasinya, tapi anak modul bisa liat konteks di mana mereka didefinisikan. Lanjutin analogi kita, bayangin aturan privasi ini kayak dapur restoran (back office): apa yang terjadi di sana itu private buat pelanggan restoran, tapi manajer bisa liat dan ngelakuin apa aja di restoran yang mereka jalanin.
Rust milih buat bikin sistem modul jalan kayak gini biar nyembunyiin detail
implementasi internal jadi default. Dengan gitu, kita tau bagian kode internal
mana yang bisa kita ubah tanpa ngerusak kode eksternalnya. Tapi, Rust tetep
ngasih kita opsi buat ngekspos bagian internal dari kode anak modul ke modul
leluhurnya pake keyword pub buat bikin item jadi public.
Mengekspos Paths dengan Keyword pub
Yuk balik lagi ke error di Listing 7-4 yang ngasih tau kita kalau modul
hosting itu private. Kita mau fungsi eat_at_restaurant di modul induknya
punya akses ke fungsi add_to_waitlist di anak modulnya, jadi kita nandain
modul hosting pake keyword pub, kayak yang ditunjukin di Listing 7-5.
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
hosting sebagai pub biar bisa dipake dari eat_at_restaurantSayangnya, kode di Listing 7-5 tetep ngasilin error compiler, kayak yang ditunjukin di Listing 7-6.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:10:37
|
10 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:13:30
|
13 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Apa yang terjadi? Nambahin keyword pub di depan mod hosting bikin modul
itu jadi public. Dengan perubahan ini, kalau kita bisa akses front_of_house,
kita bisa akses hosting. Tapi isi dari hosting itu tetep private;
bikin modul jadi public nggak bikin isinya otomatis ikutan public. Keyword
pub pada sebuah modul cuma ngebolehin kode di modul leluhurnya buat ngerujuk
ke dia, bukan buat akses kode di dalemnya. Karena modul itu adalah wadah
(container), nggak banyak yang bisa kita lakuin dengan cuma bikin modulnya jadi
public; kita perlu melangkah lebih jauh terus milih buat bikin satu atau lebih
item di dalem modulnya ikutan jadi public juga.
Error di Listing 7-6 bilang kalau fungsi add_to_waitlist itu private. Aturan
privasi berlaku buat struct, enum, fungsi, dan method, dan juga modul.
Yuk kita bikin fungsi add_to_waitlist jadi public juga dengan nambahin
keyword pub sebelum definisinya, kayak di Listing 7-7.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
pub ke mod hosting dan fn add_to_waitlist ngebolehin kita manggil fungsinya dari eat_at_restaurantSekarang kodenya bisa di-compile! Buat liat kenapa nambahin keyword pub
ngebolehin kita pake path-path ini di eat_at_restaurant sesuai sama aturan
privasi, yuk kita bahas absolute sama relative path-nya.
Di absolute path, kita mulai pake crate, yaitu akar (root) dari pohon
modul crate kita. Modul front_of_house didefinisikan di crate root. Walaupun
front_of_house itu bukan public, tapi karena fungsi eat_at_restaurant
didefinisikan di modul yang sama kayak front_of_house (artinya eat_at_restaurant
sama front_of_house itu sodaraan), kita bisa ngerujuk ke front_of_house dari
eat_at_restaurant. Terus lanjut ke modul hosting yang udah ditandain pake
pub. Kita bisa akses modul induk dari hosting, jadi kita bisa akses
hosting. Terakhir, fungsi add_to_waitlist ditandain pake pub dan kita
bisa akses modul induknya, jadi pemanggilan fungsi ini berhasil!
Di relative path, logikanya sama persis kayak absolute path kecuali buat langkah
pertama: bukannya mulai dari crate root, path-nya mulai dari front_of_house.
Modul front_of_house didefinisikan di dalem modul yang sama kayak
eat_at_restaurant, jadi relative path yang dimulai dari modul tempat
eat_at_restaurant didefinisikan itu berhasil. Terus, karena hosting sama
add_to_waitlist ditandain pake pub, sisa path-nya berhasil, dan pemanggilan
fungsi ini jadi valid!
Kalau kita berencana buat nge-share library crate kita biar project lain bisa pake kode kita, API public kita adalah kontrak kita sama user dari crate kita yang nentuin gimana mereka bisa berinteraksi sama kode kita. Ada banyak pertimbangan soal cara ngelola perubahan di API public kita buat ngebikin orang lebih gampang bergantung sama crate kita. Pertimbangan-pertimbangan ini di luar cakupan buku ini; kalau kita tertarik sama topik ini, cek The Rust API Guidelines.
Best Practices buat Packages yang Punya Binary sama Library
Kita sempet nyebut kalau sebuah package bisa punya baik crate root binary di src/main.rs maupun crate root library di src/lib.rs, dan kedua crates ini bakal punya nama yang sama secara default. Biasanya, packages dengan pola ini yang punya baik library maupun binary crate bakal punya kode di binary crate-nya secukupnya aja buat mulai executable yang manggil kode yang didefinisikan di library crate. Ini bikin project lain bisa dapet manfaat dari sebagian besar fungsionalitas yang disediain package-nya karena kode di library crate-nya bisa di-share.
Pohon modul harusnya didefinisikan di src/lib.rs. Terus, item public mana pun bisa dipake di binary crate dengan mulai path-nya pake nama package-nya. Binary crate itu jadi user dari library crate-nya sama kayak crate eksternal lainnya yang bakal pake library crate itu: dia cuma bisa pake API public-nya. Ini ngebantu kita desain API yang bagus; kita nggak cuma jadi author-nya, tapi kita juga jadi kliennya!
Di Bab 12, kita bakal nunjukin praktik organisasi ini pake program command line yang bakal isinya binary crate sekaligus library crate.
Memulai Relative Paths dengan super
Kita bisa ngebangun relative paths yang mulai dari modul induknya, bukannya
modul saat ini atau crate root, pake super di awal path-nya. Ini kayak
mulai path sistem file pake sintaks .. yang artinya naik ke direktori
induknya. Pake super ngebolehin kita ngerujuk item yang kita tau ada di
modul induknya, yang bisa ngebikin penataan ulang pohon modul jadi lebih gampang
kalau modul itu terkait erat sama induknya tapi si induk mungkin dipindah ke
tempat lain di pohon modul suatu hari nanti.
Coba liat kode di Listing 7-8 yang mensimulasikan situasi di mana seorang koki
benerin pesanan yang salah terus ngasih langsung ke pelanggannya. Fungsi
fix_incorrect_order yang didefinisikan di modul back_of_house manggil
fungsi deliver_order yang didefinisikan di modul induknya dengan nentuin
path ke deliver_order, mulai pake super.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
superFungsi fix_incorrect_order ada di modul back_of_house, jadi kita bisa pake
super buat pindah ke modul induk dari back_of_house, yang di kasus ini
adalah crate, yaitu root-nya. Dari situ, kita nyari deliver_order dan
nemuin dia. Mantap! Kita rasa modul back_of_house sama fungsi deliver_order
kemungkinan bakal tetep punya hubungan yang sama satu sama lain dan bakal
dipindah barengan kalau kita mutusin buat ngerombak pohon modul crate kita.
Makanya, kita pake super biar lebih dikit tempat yang harus di-update nanti
kalau kode ini dipindah ke modul yang beda.
Bikin Structs dan Enums Jadi Public
Kita juga bisa pake pub buat nandain structs sama enums jadi public, tapi
ada beberapa detail tambahan buat penggunaan pub bareng structs sama enums.
Kalau kita pake pub sebelum definisi struct, kita bikin struct-nya jadi
public, tapi field-field di struct-nya bakal tetep private. Kita bisa
bikin tiap field jadi public atau nggak sesuai kasusnya masing-masing. Di
Listing 7-9, kita mendefinisikan sebuah struct public back_of_house::Breakfast
dengan field toast yang public tapi field seasonal_fruit yang private.
Ini mensimulasikan kasus di restoran di mana pelanggan bisa milih roti yang
dateng bareng makanannya, tapi koki yang mutusin buah apa yang nyertain makanannya
berdasarkan musim sama stoknya. Ketersediaan buah berubah-ubah dengan cepet,
jadi pelanggan nggak bisa milih buahnya atau bahkan tau buah apa yang bakal
mereka dapet.
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast.
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like.
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal.
// meal.seasonal_fruit = String::from("blueberries");
}
Karena field toast di struct back_of_house::Breakfast itu public, di
eat_at_restaurant kita bisa nulis dan baca field toast pake notasi titik.
Perhatiin kalau kita nggak bisa pake field seasonal_fruit di eat_at_restaurant,
karena seasonal_fruit itu private. Coba di-uncomment baris yang ngubah nilai
field seasonal_fruit buat liat error apa yang bakal dapet!
Terus, perhatiin karena back_of_house::Breakfast punya field private, struct
ini harus nyediain fungsi associated yang public yang ngebikin (mengkonstruksi)
instance dari Breakfast (kita namain summer di sini). Kalau Breakfast nggak
punya fungsi kayak gitu, kita nggak bakal bisa bikin instance dari Breakfast
di eat_at_restaurant karena kita nggak bisa set nilai dari field
seasonal_fruit yang private di eat_at_restaurant.
Sebaliknya, kalau kita bikin sebuah enum jadi public, semua variannya ikutan
jadi public. Kita cuma perlu naruh pub sebelum keyword enum, kayak yang
ditunjukin di Listing 7-10.
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Karena kita bikin enum Appetizer jadi public, kita bisa pake varian Soup
sama Salad di eat_at_restaurant.
Enums nggak terlalu berguna kalau variannya nggak public; bakal nyebelin
sekali kalau harus nganotasi semua varian enum pake pub di setiap kasus, jadi
default buat varian enum adalah public. Structs biasanya berguna walaupun
field-nya nggak public, jadi field struct ngikutin aturan umum bahwa segala
hal itu private secara default kecuali dianotasi pake pub.
Ada satu lagi situasi yang ngelibatin pub yang belum kita bahas, dan itu
adalah fitur sistem modul kita yang terakhir: keyword use. Kita bakal ngebahas
use sendirian dulu, terus kita bakal nunjukin gimana cara gabungin pub sama
use.