Sistem Modul

mod, use, pub, dan aturan visibilitas antar modul.

Daftar isi

Topik ini membahas cara Rust menata kode ke dalam modul: deklarasi mod yang membentuk pohon ruang nama, perintah use yang memperpendek jalur penamaan, serta penanda pub yang mengatur item mana yang boleh diakses dari luar modulnya. Setelah membacanya, pembaca dapat membangun pohon modul pada program sendiri, menjelaskan aturan visibilitas yang berlaku pada fungsi, struktur, dan field, serta memilih tingkat keterbukaan yang sesuai untuk setiap item.

Modul sebagai ruang nama#

Modul adalah wadah bernama yang mengelompokkan fungsi, struktur, konstanta, dan modul lain sekaligus menjadi ruang nama (namespace) bagi seluruh item di dalamnya. Deklarasi mod nama { ... } membuat modul baru pada berkas yang sama, dan karena modul juga dapat memuat modul lain, keseluruhan kode membentuk sebuah pohon yang berakar pada crate. Kode di luar modul mencapai isinya melalui jalur bertingkat, misalnya loket::tiket::cetak.

Program berikut menata layanan antrean tiket loket ke dalam dua tingkat modul. Fungsi layak memakai fungsi privat nomor_berikutnya yang berada pada modul yang sama, sedangkan main memanggil item publik melalui jalur penuh dari akar crate.

rust antrean.rs
//! Program antrean tiket loket dengan modul bersarang.

mod loket {
    pub mod tiket {
        /// Mencetak tiket dengan nomor dan nama pemohon.
        pub fn cetak(nomor: u32, pemohon: &str) {
            println!("tiket nomor {nomor} untuk {pemohon}");
        }
    }

    fn nomor_berikutnya(nomor: u32) -> u32 {
        nomor + 1
    }

    /// Melayani satu pemohon dengan dua nomor tiket berurutan.
    pub fn layak(pemohon: &str) {
        tiket::cetak(1, pemohon);
        tiket::cetak(nomor_berikutnya(1), pemohon);
    }
}

fn main() {
    loket::layak("Ali");
    loket::tiket::cetak(3, "Citra");
    // pemanggilan berikut tidak dapat dikompilasi karena nomor_berikutnya privat
    // println!("{}", loket::nomor_berikutnya(4));
}
text Keluaran
tiket nomor 1 untuk Ali
tiket nomor 2 untuk Ali
tiket nomor 3 untuk Citra

Pohon modul program di atas dipetakan pada diagram berikut: akar crate memuat mod loket, dan loket memuat pub mod tiket bersama fungsi privat nomor_berikutnya. Warna hijau menandakan item publik yang dapat dijangkau lewat jalurnya, sedangkan warna oranye menandakan item privat yang hanya dapat dipakai sesama penghuni modul loket.

akar crateantrean.rsmod loketpub fn layakpub mod tiketfn nomor_berikutnya
Pohon modul program antrean

Baris komentar pada main sengaja memperlihatkan pemanggilan yang ditolak compiler. Tanpa penanda pub, nomor_berikutnya hanya terlihat oleh kode di dalam loket beserta turunannya, sehingga compiler mengeluarkan pesan error yang menyatakan bahwa fungsi tersebut privat. Penandaan ini membuat rincian internal sebuah modul tidak merembet ke luar, dan pemanggil cukup mengenal item publik seperti layak dan tiket::cetak.

Memperpendek jalur dengan use#

Perintah use membawa nama dari sebuah jalur ke dalam lingkup sehingga pemanggilan berikutnya tidak perlu menulis jalur penuh. Satu use dapat mengimpor beberapa nama sekaligus dengan daftar kurung kurawal, dan kata kunci as mengganti nama lokal ketika nama aslinya bertabrakan atau terlalu panjang. Di dalam modul, jalur relatif super:: merujuk modul induk, sedangkan jalur mutlak crate:: merujuk akar crate. Jalur mutlak tetap sah walau kode dipindah antar modul karena titik awalnya selalu berada pada akar crate.

Program berikut mengukur bingkai dari dua submodul yang saling bekerja sama. Fungsi lebar_total pada modul tepi memanggil super::sisi::lebar() untuk membaca lebar kaca yang dideklarasikan pada modul saudaranya.

rust bingkai.rs
//! Pengukuran bingkai dengan jalur super dan impor use.

mod bingkai {
    pub mod sisi {
        /// Lebar kaca dalam milimeter.
        pub fn lebar() -> u32 {
            120
        }
    }

    pub mod tepi {
        /// Tebal kayu tepi dalam milimeter.
        pub fn tebal() -> u32 {
            4
        }

        /// Lebar total kaca bersama dua tepi.
        pub fn lebar_total() -> u32 {
            super::sisi::lebar() + 2 * tebal()
        }
    }
}

use bingkai::sisi::lebar;
use bingkai::tepi::{lebar_total, tebal};

fn main() {
    println!("lebar kaca: {} mm", lebar());
    println!("tebal tepi: {} mm", tebal());
    println!("lebar total: {} mm", lebar_total());
}
text Keluaran
lebar kaca: 120 mm
tebal tepi: 4 mm
lebar total: 128 mm

Pada fungsi lebar_total, penulisan tebal() tanpa jalur dimungkinkan karena kode berada dalam modul yang sama, sementara super::sisi::lebar() harus ditulis penuh karena tujuannya berada satu tingkat ke atas. Dua perintah use di dekat main kemudian memendekkan pemanggilan dari fungsi utama. Aturan praktisnya sederhana: di dalam modul cukup sebut nama atau jalur relatif singkat, sedangkan dari luar modul gunakan use lalu panggil nama hasil impor.

Visibilitas berjenjang dengan pub#

Secara bawaan seluruh item bersifat privat: hanya kode pada modul yang sama beserta turunannya yang boleh mengaksesnya. Penanda pub membuka item bagi pemanggil di luar modul, dan Rust menyediakan bentuk antara yang membuka sebagian, yaitu pub(crate) untuk seluruh crate saat ini serta pub(super) untuk modul induk saja. Pada struktur, penanda pub pada nama struktur tidak otomatis membuka field-nya, sehingga field tetap harus diberi pub satu per satu apabila ingin ditulis dari luar.

PenandaSiapa yang dapat mengakses
tanpa pubmodul yang mendefinisikan beserta turunannya
pubsemua modul yang dapat menjangkau jalurnya
pub(crate)seluruh modul pada crate yang sama
pub(super)modul induk langsung

Program berikut menerapkan ketiganya. Field isi dibiarkan privat sehingga pembacaan hanya melalui metode ringkas, sedangkan fungsi jumlah_kata dibuka untuk seluruh crate dengan pub(crate).

rust catatan.rs
//! Aplikasi catatan dengan visibilitas berjenjang.

mod catatan {
    pub struct Entri {
        pub judul: String,
        isi: String,
    }

    impl Entri {
        /// Membuat entri baru dari judul dan isi.
        pub fn baru(judul: &str, isi: &str) -> Self {
            Entri {
                judul: judul.to_string(),
                isi: isi.to_string(),
            }
        }

        /// Ringkasan panjang isi dalam karakter.
        pub fn ringkas(&self) -> String {
            format!("{} ({} karakter)", self.judul, self.isi.len())
        }
    }

    /// Menghitung kata pada teks, hanya untuk dipakai di dalam crate ini.
    pub(crate) fn jumlah_kata(teks: &str) -> usize {
        teks.split_whitespace().count()
    }
}

fn main() {
    let entri = catatan::Entri::baru("Daftar belanja", "beras gula kopi");
    println!("{}", entri.judul);
    let ringkas = entri.ringkas();
    println!("{ringkas}");
    println!("{} kata", catatan::jumlah_kata(&ringkas));
}
text Keluaran
Daftar belanja
Daftar belanja (15 karakter)
4 kata

Fungsi main dapat membaca entri.judul karena field tersebut publik, tetapi entri.isi tidak dapat disentuh sehingga panjangnya hanya muncul lewat ringkas. Pola ini lazim dipakai untuk menjaga invariant: struktur menyimpan data mentah secara privat dan menyediakan metode publik yang menjaga format keluaran. Penanda pub(crate) berada di tengah: itemnya terbuka bagi seluruh kode pada crate yang sama sehingga bebas dipakai modul lain, namun tetap tersembunyi dari crate lain yang kelak mengimpor pustaka ini.

Kesalahan umum#

Memanggil item privat dari modul lain. Compiler menolak dengan pesan error yang menyatakan item tersebut privat, misalnya bahwa fungsi nomor_berikutnya bersifat private. Perbaikannya ada dua: tambahkan pub apabila item memang layak dipakai lintas modul, atau pindahkan pemanggilan ke modul yang sama. Jangan membuka visibilitas hanya demi menghilangkan error apabila pemanggilan tersebut menandakan letak kode yang keliru.

Menganggap field struktur ikut publik. Struktur bertanda pub tetap menyimpan field yang privat, dan penulisan entri.isi dari luar menghasilkan pesan error bahwa field tersebut privat. Tambahkan pub pada field yang memang bagian dari antarmuka publik, atau sediakan metode akses agar format keluarannya terkendali.

Menimpakan nama dengan dua use yang sama. Dua impor yang menghasilkan nama identik membuat compiler melaporkan nama tersebut ambigu. Beri nama lokal yang berbeda dengan as, atau impor satu nama saja dan biarkan nama lain tetap dipanggil dengan jalurnya.

Ringkasan#

  • Deklarasi mod membentuk pohon ruang nama, dan item diakses lewat jalur bertingkat dari akar crate.
  • Perintah use memperpendek pemanggilan, super:: merujuk modul induk, dan crate:: merujuk akar crate.
  • Item bersifat privat secara bawaan, pub membukanya, sedangkan pub(crate) dan pub(super) membuka sebagian.
  • Field struktur tidak ikut publik meskipun strukturnya publik, sehingga field perlu ditandai tersendiri.

Referensi#