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

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.

Filename: src/lib.rs
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();
}
Listing 7-3: Manggil fungsi add_to_waitlist pake absolute dan relative paths

Pertama 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
Listing 7-4: Error compiler pas nge-build kode di Listing 7-3

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.

Filename: src/lib.rs
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();
}
Listing 7-5: Mendeklarasikan modul hosting sebagai pub biar bisa dipake dari eat_at_restaurant

Sayangnya, 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
Listing 7-6: Error compiler pas nge-build kode di Listing 7-5

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.

Filename: src/lib.rs
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();
}
Listing 7-7: Nambahin keyword pub ke mod hosting dan fn add_to_waitlist ngebolehin kita manggil fungsinya dari eat_at_restaurant

Sekarang 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.

Filename: src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
Listing 7-8: Manggil fungsi pake relative path yang dimulai dengan super

Fungsi 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.

Filename: src/lib.rs
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");
}
Listing 7-9: Sebuah struct dengan beberapa field public dan beberapa field private

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.

Filename: src/lib.rs
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;
}
Listing 7-10: Nandain enum sebagai public bikin semua variannya ikutan public.

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.