Sejauh ini seluruh kode yang kita tulis hidup dalam satu scope lebar: fungsi main beserta fungsi-fungsi pendampingnya di satu berkas. Pola itu masih nyaman selama program hanya berisi belasan fungsi, tetapi aplikasi sungguhan jarang sesederhana itu. Sebuah program kasir restoran, misalnya, membutuhkan kelompok fungsi pemesanan, pengelolaan meja, manajemen stok, dan laporan penjualan — dan setiap kelompok wajar memiliki fungsi penolong bernama sama seperti hitung_total versi masing-masing. Rust menjawab kebutuhan pengelompokan ini bukan dengan class atau package sebagaimana bahasa lain, melainkan dengan module: kotak bernama yang bisa berisi fungsi, struct, enum, konstanta, bahkan module lain di dalamnya. Chapter ini mengajarkan cara mendeklarasikan module dengan keyword mod, menyusun module bersarang menjadi tree, dan mempersiapkan kita menghadapi aturan privacy bawaan Rust yang akan kita benturkan untuk pertama kalinya.

Masalah: Scope yang Semakin Penuh #

Bayangkan program kasir warung kita tumbuh menjadi tiga puluh fungsi dalam satu berkas. Fungsi hitung_total milik modul kasir dan fungsi hitung_total milik modul laporan keduanya ingin hidup di scope yang sama. Kompilator Rust menolak situasi tersebut dengan error E0428 — the name 'hitung_total' is defined multiple times — karena satu nama hanya boleh menempati satu scope sekali. Kebiasaan menempel kata pembeda seperti hitung_total_kasir dan hitung_total_laporan memang menyelesaikan galat, tetapi biayanya nyata: nama fungsi memanjang, pembaca harus menghafal kepanjangan yang tidak bermakna, dan berkas main.rs menjadi daftar fungsi datar yang sulit dinavigasi.

Module memindahkan masalah pengelompokan dari penamaan ke struktur. Alih-alih menempel embel-embel pada setiap nama, kita menaruh fungsi-fungsi ke dalam kotak bernama, lalu nama asli tetap pendek dan bentrok tidak terjadi karena kedua hitung_total kini tinggal di rumah yang berbeda.

mod: Mendeklarasikan Module dalam Satu Berkas #

Deklarasi module memakai keyword mod diikuti nama dan blok {}. Semua item yang dideklarasikan di dalam blok tersebut menjadi milik namespace module — kode di luar module tidak bisa memanggil item itu begitu saja, melainkan harus menyebut nama module di depannya dengan operator ::. Ketika mengompilasi, rustc membaca seluruh deklarasi mod, membangun tree module untuk crate, lalu memeriksa setiap pemanggilan apakah path-nya sah. Program pertama kita mengelompokkan fungsi cetak daftar menu warung:

rust
mod daftar_menu {
    pub fn tampilkan() {
        println!("== Daftar Menu Warung Kita ==");
        tampilkan_makanan();
        tampilkan_minuman();
    }

    fn tampilkan_makanan() {
        println!("1. Nasi Goreng Spesial   Rp25000");
        println!("2. Ayam Bakar Madu       Rp30000");
    }

    fn tampilkan_minuman() {
        println!("1. Es Teh Manis          Rp5000");
        println!("2. Kopi Susu Gula Aren   Rp18000");
    }
}

fn main() {
    daftar_menu::tampilkan();
}

Output yang Dihasilkan:

text
== Daftar Menu Warung Kita ==
1. Nasi Goreng Spesial   Rp25000
2. Ayam Bakar Madu       Rp30000
1. Es Teh Manis          Rp5000
2. Kopi Susu Gula Aren   Rp18000

Mari kita bedah peran setiap baris penting pada kode di atas:

  1. Baris 1 (mod daftar_menu {): keyword mod membuka deklarasi module bernama daftar_menu. Semua item hingga kurung kurawal penutup pada Baris 17 menjadi penduduk module tersebut.
  2. Baris 2 (pub fn tampilkan()): fungsi tampilkan menjadi pintu masuk module. Keyword pub di depannya membuka akses dari luar module — untuk sekarang, anggap pub sebagai tanda “boleh dipanggil dari luar”; kita bedah aturannya tuntas pada Chapter pub dan Visibilitas.
  3. Baris 4 dan Baris 5 (tampilkan_makanan(); dan tampilkan_minuman();): pemanggilan tanpa prefix apa pun berfungsi karena fungsi-fungsi itu seangkatan dengan tampilkan di dalam module yang sama.
  4. Baris 8 dan Baris 13 (fn tampilkan_makanan() dan fn tampilkan_minuman()): kedua fungsi ini sengaja dibiarkan privat. Kode di luar daftar_menu tidak dapat memanggil keduanya secara langsung; hanya tampilkan yang menjadi pintu resmi.
  5. Baris 20 (daftar_menu::tampilkan();): fungsi main memanggil item module dengan format nama_module::nama_item. Operator :: membaca “di dalam”.

Perhatikan pembagian kerja yang muncul dari struktur ini: tampilkan menjadi satu-satunya fungsi yang perlu dikenal pemanggil, sementara tampilkan_makanan dan tampilkan_minuman bekerja sebagai detail implementasi. Jika kelak kita menambah fungsi tampilkan_paket_hemat, tidak ada satu pun pemanggil lama yang berubah.

Module Bersarang Membentuk Tree #

Module boleh berisi module lain, dan penumpukan itulah yang membentuk struktur pohon. Restoran sungguhan tidak berhenti pada satu laci: area depan (front of house) yang dilihat pelanggan berbeda dari area belakang (back of house) tempat dapur bekerja. Kita susun dua cabang itu ke dalam kode:

rust
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Pelanggan masuk daftar tunggu");
        }
    }
}

fn main() {
    crate::front_of_house::hosting::add_to_waitlist();
}

Output yang Dihasilkan:

text
Pelanggan masuk daftar tunggu

Bentuk pohon yang dibentuk kode di atas — termasuk module daftar_menu dari contoh sebelumnya — dapat kita gambarkan sebagai berikut:

Istilah pohonnya perlu kita hafal bersama karena dipakai sepanjang Section ini: scope terluar disebut crate root, module di dalam module disebut child dari module pembungkusnya, dan module front_of_house adalah parent dari hosting. Diagram juga menunjukkan satu konsekuensi penting dari pohon: item-item daftar_menu dan item-item front_of_house tidak akan pernah bertabrakan nama, karena keduanya tinggal pada cabang yang berbeda. Bedah kodenya:

  1. Baris 1 (mod front_of_house {): module anak pertama dari crate root dibuat untuk mengelompokkan urusan area depan restoran.
  2. Baris 2 (pub mod hosting {): module hosting bersarang di dalam front_of_house. Keyword mod berlaku sama seperti di tingkat atas — hanya kali ini pub di depannya membuat module ini terlihat dari luar front_of_house.
  3. Baris 3 (pub fn add_to_waitlist()): fungsi tingkat ketiga pohon. Jalur menuju fungsi ini dari akar menjadi front_of_house::hosting::add_to_waitlist.
  4. Baris 10 (crate::front_of_house::hosting::add_to_waitlist();): crate:: di awal menyatakan bahwa penelusuran jalur dimulai dari crate root. Bentuk penulisan jalur ini dibahas tuntas pada Chapter berikutnya.

Benturan Pertama dengan Privacy #

Coba kita hapus semua keyword pub dari contoh terakhir — biarkan main tetap memanggil fungsi dalam. Kompilasi gagal:

rust
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {
            println!("Pelanggan masuk daftar tunggu");
        }
    }
}

fn main() {
    front_of_house::hosting::add_to_waitlist();
}

Pesan error kompilator:

text
error[E0603]: module `hosting` is private
  --> main.rs:10:21
   |
10 |     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
  --> main.rs:2:5
   |
 2 |     mod hosting {
   |     ^^^^^^^^^^^

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0603`.

Error E0603 memberi tahu penyebab pertama yang ditemui: module hosting bersifat privat. Inilah keputusan desain Rust — semua item privat secara bawaan. Item privat hanya terlihat oleh kode di module tempat item dideklarasikan beserta turunannya, sehingga front_of_house boleh memakai hosting sedangkan main tidak. Filosofi kebalikan dari bahasa yang membuka semua secara bawaan membuat setiap bagian kode yang terlihat dari luar menjadi keputusan sadar, bukan kecelakaan. Kembalikan tiga keyword pub pada module dan fungsinya, dan program kembali berjalan seperti semula.

Ringkasan #

Keyword mod mengelompokkan item ke dalam namespace bernama: fungsi-fungsi se-module saling terlihat tanpa prefix, pemanggil luar menyebut nama_module::item, dan penumpukan module membentuk tree berakar pada crate root. Kita juga sempat membenturkan diri dengan E0603 — bukti bahwa Rust menyembunyikan semua item secara bawaan dan hanya membukanya lewat keyword pub. Dua hal itu, jalur dan penglihatan, menjadi bahan Section ini: Chapter berikutnya membedah tuntas cara menulis jalur menuju item dengan path absolut crate::, path relatif, serta keyword self dan super.