Lewati ke konten

Modul di Rust

Sejauh ini, semua contoh ditulis di satu file main.rs. Untuk program kecil itu tidak masalah, tapi proyek sungguhan memecah kode menjadi bagian-bagian yang logis. Alat yang Rust sediakan untuk itu adalah sistem modul: mod, pub, dan use.

Modul adalah blok bernama yang mengelompokkan kode yang saling berhubungan:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Output:

5
▶ Coba sendiri

Ada dua hal yang perlu diperhatikan. Pertama, fungsinya dipanggil dengan math::add(...), yaitu memakai nama modul sebagai path. Kedua, fungsi itu ditandai pub. Tanpa pub, fungsi tersebut tidak bisa dipanggil dari luar modul.

Bagian ini perlu ditekankan karena sering bikin kaget: isi modul bersifat private secara default. Kalau kamu menghapus pub dari contoh di atas, kodenya tidak akan bisa di-compile:

mod math {
fn add(a: i32, b: i32) -> i32 { // tanpa `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ error: function `add` is private
println!("{sum}");
}

Pesan error dari compiler tetap dalam bahasa Inggris: “function add is private” artinya fungsi add bersifat private.

Apa pun yang ingin kamu pakai dari luar modul harus kamu tandai sendiri dengan pub. Ini memang disengaja: dengan begitu, modul bisa menyembunyikan fungsi-fungsi pembantu internalnya dan hanya membuka bagian yang memang dimaksudkan sebagai API publik.

Modul bisa berisi modul lain. Dengan cara inilah proyek yang lebih besar membangun struktur berbentuk pohon:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Coba sendiri

Menulis shapes::circle::area(...) setiap kali lama-lama melelahkan. use membawa sebuah path ke dalam cakupan (scope), sehingga kamu bisa merujuknya secara langsung:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // lebih pendek daripada shapes::circle::area
println!("{area}");
}
▶ Coba sendiri

Sebenarnya kamu sudah pernah memakai use. Ini keyword yang sama dengan yang ada di halaman HashMap (use std::collections::HashMap;).

Contoh-contoh di atas menaruh semuanya di satu file, karena Rust Playground memang mengharuskan begitu. Di proyek sungguhan di komputermu sendiri, biasanya setiap modul punya file sendiri. Kalau proyekmu punya modul math, strukturnya biasanya seperti ini:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // memberi tahu Rust: cari kode modul ini di math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (dengan titik koma, tanpa isi) menyuruh Rust memuat isi modul dari file math.rs di folder yang sama. Pola inilah yang akan kamu pakai begitu proyekmu tumbuh lebih dari satu file.

Buat modul bernama convert dengan dua fungsi publik: c_to_f (Celsius ke Fahrenheit) dan f_to_c (kebalikannya). Dari main, cetak 100°C sama dengan 212°F dan 212°F sama dengan 100°C.

Petunjuk: rumusnya c * 9.0 / 5.0 + 32.0 dan (f - 32.0) * 5.0 / 9.0. Jangan lupa pub, dan panggil fungsinya dengan nama modul, misalnya convert::c_to_f(100.0).

Lihat solusi
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C sama dengan {}°F", convert::c_to_f(100.0));
println!("212°F sama dengan {}°C", convert::f_to_c(212.0));
}

Output:

100°C sama dengan 212°F
212°F sama dengan 100°C
▶ Coba sendiri

Kedua fungsi ditandai pub, jadi main (yang berada di luar modul) bisa memanggilnya. Hapus salah satu pub, dan compiler akan memberi tahu bahwa fungsi itu private.

Buat modul bersarang school::grades dengan fungsi publik letter(score: u32) -> char yang mengembalikan 'A' untuk nilai 90 ke atas, 'B' untuk 80 ke atas, dan 'C' untuk sisanya. Di dalam grades, tulis juga fungsi pembantu private is_at_least(score: u32, min: u32) -> bool yang dipakai oleh letter. Bawa school::grades ke dalam cakupan dengan use, lalu cetak huruf nilai untuk 95, 85, dan 70.

Petunjuk: mod grades di dalam school dan fn letter sama-sama butuh pub, tapi is_at_least jangan diberi pub. Kode di dalam modul yang sama selalu bisa memanggil fungsi private.

Lihat solusi
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// private: hanya kode di dalam `grades` yang bisa memanggilnya
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Output:

95 -> A
85 -> B
70 -> C
▶ Coba sendiri

letter bisa memanggil is_at_least karena keduanya berada di modul yang sama. Dari main, grades::is_at_least(95, 90) akan gagal dengan error “function is_at_least is private”. Justru itulah tujuannya: modul hanya menampilkan API publik yang kecil dan menyimpan fungsi pembantunya untuk dirinya sendiri.

  • mod nama { ... } mengelompokkan kode yang berhubungan ke dalam sebuah modul bernama.
  • Semua yang ada di dalam modul bersifat private secara default. Tambahkan pub supaya bisa dipakai dari luar modul.
  • use membawa sebuah path ke dalam cakupan, jadi kamu tidak perlu menulisnya lengkap setiap kali.
  • Di proyek dengan banyak file, mod nama; (tanpa isi) memuat kode modul itu dari file dengan nama yang sama.
  • Kode di dalam modul selalu bisa memakai item private milik modul itu, jadi biarkan fungsi pembantu tetap private dan buka hanya yang dibutuhkan pemanggil.

Kuis singkat

1. Secara default, apakah item di dalam mod bersifat public atau private?

2. Bagaimana cara menyuruh Rust memuat kode modul dari math.rs, alih-alih menulisnya langsung di tempat?

Skor: 0 / 2