Pilihan antara pub dan privat terasa cukup selama crate kita kecil. Namun pada proyek bercabang banyak seperti struktur warung pada Chapter sebelumnya, kebutuhan nyata muncul di tengah-tengah: fungsi diskon perlu dipakai oleh seluruh module di crate kita, tetapi tidak layak diperlihatkan kepada pemakai luar yang mengimpor crate. Menandainya pub memboroskan antarmuka; membiarkannya privat memaksa kita menduplikasi logika. Rust menyediakan tingkatan di antara keduanya lewat anotasi visibility bertingkat: pub(crate), pub(super), dan pub(in path). Chapter penutup Section ini membedah ketiganya pada crate library sungguhan, memverifikasi batas-batasnya lewat cargo test, dan melihat wujud error ketika batas dilampaui.
pub(crate): Publik di dalam Crate Ini Saja #
Mekanismenya lurus: pub(crate) membuka item untuk seluruh module dalam crate yang sama — termasuk module-module yang bukan turunannya — namun tetap privat bagi dunia luar. Fungsi bertanda ini bebas dipanggil dari module mana pun di crate, seolah pub; begitu crate dikompilasi sebagai library dan diimpor pihak lain, fungsi itu berperilaku privat penuh. Anotasi semacam ini lazim untuk helper internal, struct penolong antar-module, hingga seluruh module util yang tidak ingin menjadi API publik.
Kita bangun crate library kasir yang memakainya. Struktur proyeknya ringkas:
kasir/
├── Cargo.toml
└── src/
├── lib.rs
└── pembayaran.rsBerkas src/lib.rs menampung fungsi perhitungan inti, termasuk diskon yang hanya urusan internal crate:
pub mod pembayaran;
pub fn hitung_total(harga_satuan: u32, jumlah: u32) -> u32 {
harga_satuan * jumlah
}
pub(crate) fn terapkan_diskon(total: u32) -> u32 {
if total >= 100000 {
total - 10000
} else {
total
}
}
pub fn tagihan_akhir(harga_satuan: u32, jumlah: u32) -> u32 {
let total = hitung_total(harga_satuan, jumlah);
terapkan_diskon(total)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn total_sesuai() {
assert_eq!(hitung_total(25000, 2), 50000);
}
#[test]
fn diskon_berlaku() {
assert_eq!(terapkan_diskon(150000), 140000);
}
}Bedah kode di atas:
- Baris 3 (
pub fn hitung_total(...)): fungsi publik sejati — bagian antarmuka cratekasiryang sah dipakai siapa pun. - Baris 7 (
pub(crate) fn terapkan_diskon(...)): fungsi diskon terbuka untuk seluruh isi crate ini. Modulepembayaran, module test, dan fungsi apa pun di crate boleh memanggilnya; pemakai eksternal tidak. - Baris 15 (
pub fn tagihan_akhir(...)): pintu publik yang membungkus logika internal — pola khas crate sehat: fungsipubmenyusun hasil dari kombinasi helperpub(crate)dan privat. - Baris 20 (
#[cfg(test)]): unit test hanya dikompilasi saat menjalankancargo test, bukan saat crate dipakai. Test pada Baris 30 membuktikanterapkan_diskonterlihat dari module test di crate yang sama.
pub(super) dan pub(in path) untuk Batas Lebih Ketat #
Dua anotasi lain membatasi lebih sempit dari pub(crate). pub(super) membuka item hanya untuk module induk langsung beserta turunannya; pub(in path) membuka item untuk module tertentu yang kita sebut path-nya beserta seluruh subtree-nya — misalnya pub(in crate::pembayaran) berlaku untuk semua kode di bawah cabang pembayaran. Keduanya hidup di module bersarang, jadi kita isi src/pembayaran.rs:
pub mod qris {
pub fn bayar(total: u32) {
println!("QRIS: Rp{} berhasil dibayar", total);
}
}
pub mod validasi {
pub(super) fn nominal_valid(total: u32) -> bool {
total > 0
}
}
mod tarif {
pub(in crate::pembayaran) fn layanan(total: u32) -> u32 {
total / 100
}
}
pub fn proses(total: u32) {
if validasi::nominal_valid(total) {
let biaya_layanan = tarif::layanan(total);
qris::bayar(total + biaya_layanan);
} else {
println!("Nominal tidak valid, transaksi dibatalkan");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn biaya_layanan_seperseratus() {
assert_eq!(tarif::layanan(50000), 500);
}
#[test]
fn nominal_nol_ditolak() {
assert!(!validasi::nominal_valid(0));
assert!(validasi::nominal_valid(1));
}
}Bedah kode di atas:
- Baris 8 (
pub(super) fn nominal_valid(...)): fungsi di modulevalidasiterlihat oleh module induknya —pembayaran— dan turunannya saja. Karenavalidasisendiri bertandapub mod, pemakai luar dapat melihat module-nya, tetapi fungsinominal_validtetap tersembunyi bagi mereka. - Baris 14 (
pub(in crate::pembayaran) fn layanan(...)): fungsi di module privattarifdibuka untuk seluruh subtreepembayaran. Moduletestsberada di dalam subtree itu, sehingga pengujian pada Baris 34 sah. - Baris 19 (
pub fn proses(...)): fungsi orkestrator publik menggabungkan ketiga lapisan — memanggil validasipub(super), tarifpub(in ...), dan qris publik. Pemakai luar cukup mengenalproses. - Baris 20–22 (
if validasi::nominal_valid(total)): pemanggilan dari modulepembayaransendiri — tepat wilayah yang diizinkan kedua anotasi.
Verifikasi Batas lewat cargo test #
Klaim-klaim di atas bukan sekadar janji — kita bisa membuktikannya dengan cargo test. Perintah ini mengompilasi crate beserta seluruh module testnya lalu menjalankan semuanya:
cargo testOutput yang Dihasilkan:
running 4 tests
test pembayaran::tests::nominal_nol_ditolak ... ok
test pembayaran::tests::biaya_layanan_seperseratus ... ok
test tests::diskon_berlaku ... ok
test tests::total_sesuai ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sEmpat test lolos — dua dari module test di lib.rs yang memanggil terapkan_diskon bertanda pub(crate), dan dua dari module test di pembayaran.rs yang memanggil item bertanda pub(super) serta pub(in ...). Di depan baris running 4 tests, cargo juga menampilkan status kompilasi, dan di bagian akhir selalu mengikuti Doc-tests kasir yang menjalankan contoh kode pada dokumentasi — untuk crate kita masih kosong.
Ketika Batas Dilampaui dari Luar Crate #
Bukti kedua: akses dari luar crate memang tertolak. Integration test pada folder tests/ dikompilasi sebagai crate terpisah yang mengimpor kasir layaknya pemakai eksternal, sehingga cocok menjadi penguji batas. Berkas tests/akses_eksternal.rs berikut mencoba mengimpor fungsi internal:
use kasir::terapkan_diskon;
#[test]
fn akses_diskon_dari_luar() {
assert_eq!(terapkan_diskon(150000), 140000);
}Pesan error ketika cargo test dijalankan (baris status Compiling di depan memuat lokasi proyek yang berbeda-beda di tiap komputer, jadi tidak dikutip):
error[E0603]: function `terapkan_diskon` is private
--> tests/akses_eksternal.rs:1:12
|
1 | use kasir::terapkan_diskon;
| ^^^^^^^^^^^^^^^ private function
|
note: the function `terapkan_diskon` is defined here
--> src/lib.rs:7:1
|
7 | pub(crate) fn terapkan_diskon(total: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `kasir` (test "akses_eksternal") due to 1 previous error
warning: build failed, waiting for other jobs to finish...Pesan E0603 yang sama dengan kasus privat biasa muncul — dari luar crate, pub(crate) memang tampak sebagai fungsi privat. Perhatikan baris catatan: kompilator menunjuk persis deklarasi pub(crate) di lib.rs, jadi sumber penolakannya jelas. Baris terakhir warning: build failed, waiting for other jobs to finish... adalah pesan cargo bahwa target lain yang sedang dikompilasi paralel ditunggu sampai selesai sebelum cargo berhenti. Hapus berkas test itu untuk mengembalikan proyek ke hijau.
Batas pub(super) juga bisa kita patahkan dari dalam crate sendiri. Tambahkan fungsi berikut di baris paling akhir src/lib.rs, lalu jalankan cargo build:
pub fn cek_validasi(total: u32) -> bool {
crate::pembayaran::validasi::nominal_valid(total)
}Pesan error kompilator:
error[E0603]: function `nominal_valid` is private
--> src/lib.rs:36:34
|
36 | crate::pembayaran::validasi::nominal_valid(total)
| ^^^^^^^^^^^^^ private function
|
note: the function `nominal_valid` is defined here
--> src/pembayaran.rs:8:5
|
8 | pub(super) fn nominal_valid(total: u32) -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `kasir` (lib) due to 1 previous errorError yang sama, penolak yang berbeda: crate root bukan wilayah super dari module validasi — super dari validasi adalah pembayaran — sehingga fungsi itu tidak terlihat dari lokasi baru. Nomor baris 36 pada pesan mengikuti posisi fungsi tambahan di akhir lib.rs. Anotasi visibility menempel pada posisi deklarasi, bukan pada niat pemanggil.
Ringkasan Tiga Anotasi #
Tabel berikut merangkum wilayah kerja setiap anotasi pada deklarasi pub(...):
| Anotasi | Terlihat oleh | Contoh wajar penggunaan |
|---|---|---|
pub(crate) | Seluruh module dalam crate ini | Helper lintas module, module util internal |
pub(super) | Module induk langsung dan turunannya | Step internal milik module anak |
pub(in crate::x) | Subtree module x yang disebut | Fungsi khusus satu cabang besar |
Ringkasan #
Anotasi visibility bertingkat mengisi ruang antara pub dan privat: pub(crate) membuka item untuk seluruh isi crate namun tertutup bagi pemakai luar, pub(super) membatasi pada module induk, dan pub(in path) membatasi pada subtree tertentu. Batas-batas itu terverifikasi lewat cargo test dari dalam dan error E0603 dari luar crate — bukti bahwa promise visibility ditegakkan compiler, bukan konvensi. Dengan ini perjalanan Section Modul dan Visibilitas tuntas: dari mod pertama hingga antarmuka berjenjang, kita kini mampu menyusun crate yang rapi di dalam dan terkendali dari luar — bekal yang langsung terpakai ketika Section berikutnya membahas crate sebagai unit distribusi bersama Cargo.