Fungsi pada Chapter sebelumnya selalu melakukan pekerjaan yang persis sama setiap kali dipanggil: teks yang sama, angka yang sama, hasil yang sama. Aplikasi nyata butuh lebih dari itu, misalnya satu fungsi tampilkan_harga yang mampu mencetak harga barang apa pun, bukan hanya harga kopi. Solusinya adalah mengirim data ke dalam fungsi lewat parameter. Pada Chapter ini kita akan membedakan parameter dengan argument, menulis tipe secara eksplisit, dan menangani galat ketika tipe data yang dikirim tidak cocok.
Membedakan Parameter dan Argumen #
Dua istilah ini sering dipakai bergantian, tetapi keduanya merujuk pada momen yang berbeda:
- Parameter adalah nama dan tipe yang kita deklarasikan pada definisi fungsi. Parameter adalah “tempat duduk” yang menunggu diisi.
- Argument adalah nilai nyata yang kita kirim saat memanggil fungsi. Argument adalah penumpang yang duduk di kursi tersebut.
Contohnya, pada definisi fn tampilkan_harga(nama_barang: &str, harga: i64), nama_barang dan harga adalah parameter. Pada pemanggilan tampilkan_harga("Kopi Susu", 25000), string "Kopi Susu" dan angka 25000 adalah argument. Saat runtime menjalankan tubuh fungsi, setiap penyebutan nama_barang di dalam tubuh akan membaca nilai "Kopi Susu" yang dikirim pemanggil.
Menulis Parameter dengan Tipe Eksplisit #
Rust mewajibkan setiap parameter memiliki tipe yang ditulis eksplisit di dalam definisi fungsi, dengan format nama_parameter: tipe. Mari kita lihat wujudnya:
fn tampilkan_harga(nama_barang: &str, harga: i64) {
println!("{}: Rp{}", nama_barang, harga);
}
fn main() {
tampilkan_harga("Kopi Susu", 25000);
tampilkan_harga("Teh Tarik", 20000);
}Output yang Dihasilkan:
Kopi Susu: Rp25000
Teh Tarik: Rp20000Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1 (
fn tampilkan_harga(nama_barang: &str, harga: i64) {): Fungsi dideklarasikan dengan dua parameter. Parameternama_barangbertipe&str(teks) dan parameterhargabertipei64(bilangan bulat). Tanda koma memisahkan kedua parameter. - Baris 2 (
println!("{}: Rp{}", nama_barang, harga);): Tubuh fungsi mencetak kedua parameter menggunakan{}sebagai penanda tempat nilai dicetak. - Baris 6 (
tampilkan_harga("Kopi Susu", 25000);): Pemanggil pertama mengirim argument"Kopi Susu"ke parameternama_barangdan25000ke parameterharga. Nilai keduanya hanya berlaku untuk pemanggilan ini. - Baris 7 (
tampilkan_harga("Teh Tarik", 20000);): Pemanggil kedua mengirim nilai yang berbeda ke parameter yang sama, sehingga fungsi yang sama menghasilkan keluaran yang berbeda.
Satu definisi fungsi kini melayani banyak barang. Inilah manfaat utama parameter: logika ditulis sekali, datanya berganti-ganti.
Mengirim Banyak Argument #
Parameter boleh lebih dari satu selama jumlah dan urutannya sama dengan argument yang dikirim. Mari kita naikkan kompleksitas sedikit dengan fungsi yang menghitung total belanja:
fn hitung_total(harga_satuan: i64, jumlah: i64) {
let total = harga_satuan * jumlah;
println!("{} x {} = Rp{}", jumlah, harga_satuan, total);
}
fn main() {
hitung_total(25000, 4);
hitung_total(12000, 10);
}Output yang Dihasilkan:
4 x 25000 = Rp100000
10 x 12000 = Rp120000Mari kita telaah bagian penting dari kode di atas:
- Baris 1 (
fn hitung_total(harga_satuan: i64, jumlah: i64) {): Dua parameter bertipe sama tetap harus dideklarasikan terpisah; tipei64tidak otomatis menular dari parameter pertama ke parameter kedua. - Baris 2 (
let total = harga_satuan * jumlah;): Variabel lokaltotaldibuat di dalam fungsi untuk menampung hasil perkalian kedua parameter. Variabeltotalhanya dikenal di dalam fungsi ini. - Baris 7 (
hitung_total(25000, 4);): Argument pertama25000masuk keharga_satuandan argument kedua4masuk kejumlah, karena pencocokan dilakukan berdasarkan posisi (urutan), bukan nama.
Pencocokan berdasarkan posisi ini penting diingat. Kalau kita tertukar menulis hitung_total(4, 25000), program tetap terkompilasi dan berjalan, tetapi makna datanya ikut tertukar. Rust tidak bisa menebak niat kita; Rust hanya mengikuti urutan yang kita tulis.
Argument Tidak Harus Literal #
Sejauh ini kita selalu mengirim nilai literal seperti "Kopi Susu" dan 25000. Padahal argument adalah posisi expression, sehingga apa pun yang mengevaluasi menjadi tipe yang cocok boleh dikirim — termasuk isi variabel:
fn buat_label(nama: &str, harga: i64) {
println!("[{}] Rp{}", nama, harga);
}
fn main() {
let nama_barang = "Roti Bakar";
let harga = 15000;
buat_label(nama_barang, harga);
buat_label("Es Jeruk", 12000);
}Output yang Dihasilkan:
[Roti Bakar] Rp15000
[Es Jeruk] Rp12000Mari kita telaah pemanggilan pada kode di atas:
- Baris 9 (
buat_label(nama_barang, harga);): Nilai variabelnama_barang(“Roti Bakar”) danharga(15000) dibaca lebih dulu, lalu nilai-nilai tersebut dikirim sebagai argument. Fungsi tidak tahu dan tidak peduli apakah argument berasal dari literal atau variabel; fungsi hanya menerima nilai. - Baris 10 (
buat_label("Es Jeruk", 12000);): Pemanggilan kedua mencampur literal dengan konstanta angka. Pola campuran seperti ini sangat umum saat sebagian data berasal dari pengguna dan sebagian lagi tetap di kode.
Kemampuan mengirim isi variabel sebagai argument inilah yang membuat fungsi benar-benar berguna: data yang dikumpulkan di satu tempat dapat diolah oleh fungsi yang sama berkali-kali dengan isi berbeda.
Tipe Argument Harus Cocok #
Kompiler Rust memeriksa tipe setiap argument terhadap tipe parameter sebelum program berjalan. Pemeriksaan ini menghapus seluruh kelas galat “angka dan teks tercampur” yang sering muncul di bahasa lain. Perhatikan percobaan berikut yang sengaja salah:
fn tampilkan_harga(nama_barang: &str, harga: i64) {
println!("{}: Rp{}", nama_barang, harga);
}
fn main() {
tampilkan_harga("Kopi Susu", 25000.5);
}Kode tersebut gagal dikompilasi dan kompiler menampilkan:
error[E0308]: mismatched types
--> src/main.rs:6:34
|
6 | tampilkan_harga("Kopi Susu", 25000.5);
| --------------- ^^^^^^^ expected `i64`, found floating-point number
| |
| arguments to this function are incorrectPesan galat tersebut sangat spesifik: kompiler menunjuk argument kedua, menyebutkan tipe yang diharapkan (i64), dan tipe yang dikirim (floating-point number). Memanggil fungsi di Rust ibarat memasukkan kunci ke gembok — bentuknya harus persis, atau pintu tidak akan terbuka sama sekali.
Makna Tanda & pada Parameter Teks #
Kita sempat menuliskan &str tanpa menjelaskan tanda &. Tanda tersebut berarti fungsi menerima referensi ke data teks, bukan memiliki datanya. Berkat referensi, string yang sama dapat dipakai berulang kali tanpa dimusnahkan setelah pemanggilan pertama. Untuk sekarang, cukup ingat pola ini: parameter teks ditulis &str, dan argument yang dikirim cukup string biasa seperti "Kopi Susu".
Ringkasan #
Parameter adalah placeholder bertipe yang dideklarasikan pada definisi fungsi, sedangkan argument adalah nilai nyata yang dikirim saat pemanggilan. Rust mencocokkan keduanya berdasarkan posisi, memeriksa tipe setiap argument sebelum kompilasi selesai, dan menolak argument yang jumlah atau tipenya tidak sesuai. Fungsi kita kini bisa menerima data; langkah berikutnya adalah membuat fungsi mengirim data kembali ke pemanggil melalui return value.