Sejauh ini seluruh kode yang kita tulis hidup di dalam satu fungsi bernama main. Aplikasi nyata tidak bisa berkembang dengan cara itu: teks sambutan, menu pilihan, dan laporan penjualan akan menumpuk menjadi satu blok raksasa yang sulit dibaca dan diubah. Fungsi memecah program menjadi bagian-bagian kecil yang punya nama, sehingga kita cukup memanggil nama tersebut setiap kali butuh pekerjaan yang sama diulang. Pada Chapter ini kita akan belajar mendefinisikan fungsi pertama dengan kata kunci fn, memberi nama sesuai konvensi Rust, dan memanggil fungsi tersebut dari fungsi lain.

Masalah Kode yang Menumpuk di main #

Bayangkan program kasir yang harus mencetak sambutan, menu, dan struk pembelian. Kalau seluruh logika tersebut ditulis berurutan di dalam main, tiga masalah muncul dengan cepat. Pertama, teks sambutan yang dipakai di dua tempat harus disalin ulang, sehingga setiap perubahan teks harus dilakukan dua kali. Kedua, nama variabel sementara seperti teks dan nomor saling menimpa karena berbagi satu ruang yang sama. Ketiga, pembaca kode harus memahami seluruh isi main dari awal hingga akhir sebelum tahu bagian mana yang mencetak struk.

Fungsi menyelesaikan ketiga masalah tersebut sekaligus. Sebuah fungsi adalah blok kode bernama yang diberi tugas spesifik, misalnya mencetak menu atau menghitung total. Blok main tinggal memanggil nama-nama fungsi itu secara berurutan, dan setiap fungsi dapat dipanggil berkali-kali tanpa menyalin kode.

Anatomi Fungsi Rust #

Fungsi di Rust didefinisikan dengan kata kunci fn, diikuti nama fungsi, tanda kurung, lalu sepasang kurung kurawal yang membungkus tubuh fungsi (function body). Berikut bentuk paling sederhana:

rust
fn sapa() {
    println!("Selamat datang di aplikasi kasir!");
}

Fungsi di atas bernama sapa, tidak menerima masukan apa pun (ditandai tanda kurung kosong), dan tubuhnya berisi satu pemanggilan println!. Bagian di dalam kurung kurawal hanya dijalankan ketika fungsi dipanggil, bukan saat fungsi didefinisikan. Rust membaca definisi fungsi untuk mencatat nama dan bentuknya saja; eksekusi baru terjadi saat ada pemanggilan.

Untuk memanggil fungsi, kita cukup menuliskan nama fungsi tersebut diikuti tanda kurung dan titik koma, misalnya sapa();. Pemanggilan boleh dilakukan dari fungsi mana pun, termasuk dari dalam main maupun dari dalam fungsi lain.

Konvensi Penamaan snake_case #

Rust menganut konvensi penamaan snake_case untuk fungsi: seluruh huruf ditulis kecil, dan setiap kata dipisahkan garis bawah. Konvensi ini bukan sekadar gaya, karena kompiler Rust benar-benar memeriksa penamaan tersebut.

Bentuk namaPenilaian Rust
tampilkan_menuSesuai konvensi, tanpa peringatan
hitung_total_belanjaSesuai konvensi, tanpa peringatan
TampilkanMenuMemicu peringatan non_snake_case
HitungTotalMemicu peringatan non_snake_case

Kalau kita mendefinisikan fungsi bernama TampilkanMenu, kompiler Rust tetap menghasilkan program yang berjalan, tetapi kompiler menampilkan peringatan seperti ini:

text
warning: function `TampilkanMenu` should have a snake case name
  |
1 | fn TampilkanMenu() {
  |    ^^^^^^^^^^^^^ help: convert the identifier to snake case: `tampilkan_menu`

Peringatan tersebut bukan galat; program tetap dikompilasi. Namun kode produksi yang sehat selalu bebas peringatan, jadi kita sebaiknya mengikuti konvensi snake_case sejak awal.

Memanggil Fungsi dari main #

Sekarang kita rakit dua fungsi sederhana dan panggil keduanya dari main. Simpan kode berikut sebagai berkas kasir.rs, lalu kompilasi dan jalankan:

rust
fn sapa() {
    println!("Selamat datang di aplikasi kasir!");
}

fn tampilkan_menu() {
    println!("1. Tambah barang");
    println!("2. Hapus barang");
}

fn main() {
    sapa();
    tampilkan_menu();
}

Output yang Dihasilkan:

text
Selamat datang di aplikasi kasir!
1. Tambah barang
2. Hapus barang

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 1 (fn sapa() {): Kompiler mencatat bahwa ada fungsi bernama sapa yang tidak menerima masukan dan tubuhnya diapit kurung kurawal.
  2. Baris 5 (fn tampilkan_menu() {): Kompiler mencatat fungsi kedua bernama tampilkan_menu dengan bentuk yang sama.
  3. Baris 10 (fn main() {): Kompiler menandai main sebagai titik masuk program, sehingga eksekusi selalu dimulai dari fungsi ini.
  4. Baris 11 (sapa();): Runtime menjalankan tubuh fungsi sapa, mencetak satu baris sambutan, lalu kembali ke main.
  5. Baris 12 (tampilkan_menu();): Runtime menjalankan tubuh fungsi tampilkan_menu, mencetak dua baris menu, lalu kembali ke main. Setelah baris ini, main selesai dan program berakhir.

Perhatikan urutan baris pada output mengikuti urutan pemanggilan di main, bukan urutan definisi fungsi di berkas. Definisi hanyalah katalog; pemanggilan adalah perintah eksekusi.

Memanggil Fungsi dari Fungsi Lain #

Fungsi juga boleh memanggil fungsi lain, tidak hanya dipanggil oleh main. Pola ini memungkinkan kita menyusun laporan yang terdiri dari bagian kepala, isi, dan kaki:

rust
fn print_footer() {
    println!("==========================");
}

fn print_report() {
    print_header();
    println!("Total: Rp150000");
    print_footer();
}

fn print_header() {
    println!("=== Laporan Penjualan ===");
}

fn main() {
    print_report();
}

Output yang Dihasilkan:

text
=== Laporan Penjualan ===
Total: Rp150000
==========================

Mari kita telaah alur pemanggilan pada kode di atas:

  1. Baris 15 (fn main() {): Eksekusi program dimulai dari main.
  2. Baris 16 (print_report();): Runtime berpindah ke fungsi print_report dan mulai menjalankan tubuhnya baris demi baris.
  3. Baris 6 (print_header();): Di dalam print_report, pemanggilan ini menjalankan fungsi print_header yang terletak setelah print_report di berkas. Posisi definisi yang belakangan tidak menjadi masalah.
  4. Baris 7–8: Baris teks total dan pemanggilan print_footer() mencetak isi serta kaki laporan secara berurutan, lalu print_report selesai dan kontrol kembali ke main.

Contoh di atas memperlihatkan sifat penting Rust: urutan penulisan definisi fungsi di berkas bebas. print_report dapat memanggil print_header walau print_header baru dideklarasikan di baris ke-11, karena kompiler membaca seluruh berkas sebelum menghasilkan program.

Ringkasan #

Fungsi didefinisikan dengan kata kunci fn, diberi nama snake_case, dan tubuhnya dijalankan hanya saat dipanggil dengan nama_fungsi();. Eksekusi program selalu dimulai dari main, dan setiap fungsi boleh memanggil fungsi lain tanpa memedulikan urutan definisi. Dengan bekal ini, kita siap mengirim data ke dalam fungsi melalui parameter dan argument pada Chapter berikutnya.