Crates and modules
Setiap kali kita menuliskan code di Rust, kita menuliskannya di dalam crate
. crate
adalah sebuah file, atau banyak file, yang berjalan bersama code kita. Di dalam file yang Anda tulis, Anda juga bisa membuat mod
. mod
adalah wadah untuk function, struct, dll. Dan ia digunakan untuk beberapa alasan:
- Menuliskan code: ia membantu Anda untuk memikirkan tentang gambara besar dari code yang ingin Anda buat. Ini menjadi sangat penting sebagaimana code Anda semakin membesar dan terus membesar.
- Membaca code: orang lain jadi bisa memahami code yang kita tuliskan dengan sangat mudah. Contohnya, nama
std::collections::HashMap
memberitahukan kita bahwa ia merupakan bagian daristd
dan di dalam sebuah module bernamacollections
. Ini memberikan Anda petunjuk bahwa mungkin ada lebih banyak type collection di dalamcollections
yang bisa Anda coba. - Privasi: semua dimulai dengan private. Hal ini memungkinkan Anda untuk mencegah pengguna menggunakan function secara langsung.
Untuk membuat mod
, cukup tuliskan mod
dan mulai code block dengan menggunakan {}
. Kita akan membuat sebuah mod bernama print_things
yang memiliki beberapa function yang berkaitan dengan cetak-mencetak.
mod print_things { use std::fmt::Display; fn prints_one_thing<T: Display>(input: T) { // Print apapun yang mengimplementasikan Display println!("{}", input) } } fn main() {}
Anda bisa melihat bahwa kita menuliskan use std::fmt::Display;
di dalam print_things
, karena ia adalah tempat yang terpisah. Jika Anda menuliskan use std::fmt::Display;
di dalam main()
, hal itu tidaklah berguna. Juga, sekarang ini kita tidak bisa memanggil function prints_one_thing
dari function main()
. Tanpa menggunakan keyword pub
di depan fn
ia akan tetap bersifat private. Mari kita coba memanggilnya tanpa pub
. Ini adalah salah satu cara untuk menuliskannya:
// 🚧 fn main() { crate::print_things::prints_one_thing(6); }
crate
artinya "di dalam project ini", atau dengan bahasa yang lebih sederhana "di dalam file ini". Di dalam crate
tersebut terdapat mod bernama print_things
, dan kemudian ada function prints_one_thing()
. Anda bisa menuliskannya seperti itu setiap saat, atau Anda bisa menuliskannnya menggunakan use
mengimportnya. Sekarang kita bisa melihat error yang mengatakan bahwa ia bersifat private:
// ⚠️ mod print_things { use std::fmt::Display; fn prints_one_thing<T: Display>(input: T) { println!("{}", input) } } fn main() { use crate::print_things::prints_one_thing; prints_one_thing(6); prints_one_thing("Trying to print a string...".to_string()); }
Berikut adalah pesan errornya:
error[E0603]: function `prints_one_thing` is private
--> src\main.rs:10:30
|
10 | use crate::print_things::prints_one_thing;
| ^^^^^^^^^^^^^^^^ private function
|
note: the function `prints_one_thing` is defined here
--> src\main.rs:4:5
|
4 | fn prints_one_thing<T: Display>(input: T) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sangatlah mudah untuk memahami bahwa function prints_one_thing
adalah private. Pesan error tersebut juga menuliskan src\main.rs:4:5
yaitu letak dimana functionnya berada. Ini cukup membantu karena Anda bisa menulis mod
tidak hanya dalam satu file, tetapi bisa ditulisakn di banyak file juga.
Sekarang kita menuliskan pub fn
menggantikan fn
, dan codenya bekerja.
mod print_things { use std::fmt::Display; pub fn prints_one_thing<T: Display>(input: T) { println!("{}", input) } } fn main() { use crate::print_things::prints_one_thing; prints_one_thing(6); prints_one_thing("Trying to print a string...".to_string()); }
Hasil printnya adalah:
6
Trying to print a string...
Bagaimana pub
pada sebuah struct, enum, trait, atau module? pub
bekerja seperti ini untuk mereka masing-masing:
pub
pada sebuah struct: ia membbuat structnya menjadi public, namun item-item di dalamnya tidaklah public. Untuk membuat itemnya public, Anda perlu menuliskanpub
pada setiap item tersebut.pub
pada sebuah enum atau trait: semuanya menjadi public. Ini sangatlah masuk akal, karena trait adalah tentang memberikan perilaku yang sama pada sesuatu. Dan enum adalah tentang memilih salah satu dari banyaknya item, dan Anda perlu melihat semua pilihan tersebut untuk memilihnya.pub
pada sebuah module: top level module semestinya akan menjadipub
karena jika ia bukanlah pub maka tidak ada yang bisa menyentuh apapun yang berada di dalamnya. Tapi module yang berada di dalam module memerlukanpub
untuk menjadi public.
Jadi, mari kita tambahkan sebuah struct bernama Billy
di dalam print_things
. Struct ini hampir semua isinya akan menjadi bersifat public, namun tidak sepenuhnya. Struct sendiri adalah public, sehingga tentu saja kita nantinya akan menuliskan pub struct Billy
. Di dalam strust tersebut terdapat field name
dan times_to_print
. name
tidaklah public, karena kita hanya menginginkan user membuat struct dengan nama "Billy".to_string()
. Tapi pengguna dapat memilih berapa kali untuk mencetak, sehingga times_to_print
akan menjadi publik. Codenya terlihat seperti ini:
mod print_things { use std::fmt::{Display, Debug}; #[derive(Debug)] pub struct Billy { // Billy adalah public name: String, // name adalah private. pub times_to_print: u32, } impl Billy { pub fn new(times_to_print: u32) -> Self { // Ini artinya user perlu untuk menggunakan new untuk membuat Billy. User hanya bisa mengubah angka dari times_to_print Self { name: "Billy".to_string(), // Kita pilihkan namanya - user tidak bisa memilihkannya times_to_print, } } pub fn print_billy(&self) { // function ini akan mencetak Billy for _ in 0..self.times_to_print { println!("{:?}", self.name); } } } pub fn prints_one_thing<T: Display>(input: T) { println!("{}", input) } } fn main() { use crate::print_things::*; // Sekarang kita menggunakan *. Ini mengimport semua dari print_things let my_billy = Billy::new(3); my_billy.print_billy(); }
This will print:
"Billy"
"Billy"
"Billy"
Ah ya, tanda *
yang digunakan untuk mengimport semuanya disebut sebagai "glob operator". Glob artinya "global", sehingga itu berarti seluruhnya/semuanya.
Di dalam sebuah mod
Anda bisa membuat mod yang lain. Child mod (mod yang berada di dalam mod) selalu bisa menggunakan apapun yang ada di dalam parent mod. Anda bisa melihat ini pada contoh selanjutnya dimana kita memiliki mod city
di dalam mod province
di dalam mod country
.
Anda bisa membayangkan strukturnya seperti ini: Meskipun Anda berada di sebuah negara, itu bukan berarti Anda berada di sebuah provinsinya. Dan bahkan jika Anda berada di sebuah provinsi, bukan berarti Anda di dalam kota. Tapi jika Anda berada di dalam kota, maka sudah dipastikan bahwa Anda berada di dalam suatu provinsi dan berada di dalam suatu negara.
mod country { // mod utama tidak memerlukan pub fn print_country(country: &str) { // Catatan: function ini tidaklah public println!("We are in the country of {}", country); } pub mod province { // buat mod ini menjadi mod public fn print_province(province: &str) { // Catatan: function ini bukanlah public println!("in the province of {}", province); } pub mod city { // buat mod ini menjadi mod public pub fn print_city(country: &str, province: &str, city: &str) { // function ini bersifat public crate::country::print_country(country); crate::country::province::print_province(province); println!("in the city of {}", city); } } } } fn main() { crate::country::province::city::print_city("Canada", "New Brunswick", "Moncton"); }
Bagian menariknya adalah bahwa print_city
bisa mengakses print_province
dan print_country
. Itu dikarenakan mod city
berada di dalam mod-mod lainnya. Ia tidak memerlukan pub
di depan print_province
untuk menggunakannya. Dan ini sangatlah masuk akal: city tidaklah perlu melakukan apapun untuk berada di dalam province dan di dalam country.
Anda juga mungkin menyadari bahwa crate::country::province::print_province(province);
sangatlah panjang. Di saat kita berada di dalam sebuah module, kita bisa menggunakan super
untuk membawa item yang berada di level atas. Sebenarnya kata super
sendiri artinya adalah "atas", seperti pada kata "superior" (peringkat atas). Pada contoh kita ini, kita hanya menggunakan functionnya sekali saja. Namun jika Anda menggunakannya lebih dari sekali, adalah ide yang bagus untuk mengimportnya. Dan tentu saja ini merupakan hal yang baik pula jika membuat codenya menjadi lebih mudah untuk dibaca, meskipun Anda hanya menggunakan fungsinya hanya satu kali saja. Code yang ada di bawah ini hampir sama, tapi sedikit lebih mudah dibaca:
mod country { fn print_country(country: &str) { println!("We are in the country of {}", country); } pub mod province { fn print_province(province: &str) { println!("in the province of {}", province); } pub mod city { use super::super::*; // gunakan semua yang berada di "above above": yang mana itu artinya adalah mod country use super::*; // gunakan semua yang berada di "above": yang artinya itu adalah mod province pub fn print_city(country: &str, province: &str, city: &str) { print_country(country); print_province(province); println!("in the city of {}", city); } } } } fn main() { use crate::country::province::city::print_city; // bawa functionnya ke sini print_city("Canada", "New Brunswick", "Moncton"); print_city("Korea", "Gyeonggi-do", "Gwangju"); // Dengan cara seperti ini, kita tidak perlu menuliskannya secara panjang jika ingin menggunakannya lagi }