Fungsi yang kita buat sejauh ini bekerja seperti papan pengumuman: menerima data, lalu mencetaknya langsung ke layar. Pola itu cepat menemui batasnya. Bayangkan fungsi hitung_diskon yang langsung mencetak hasilnya — pemanggil tidak bisa memakai angka diskon tersebut untuk menghitung total bayar, mencetak struk, atau menyimpan laporan, karena angkanya sudah terlanjur berubah menjadi teks di layar. Fungsi yang baik memisahkan dua tanggung jawab: menghitung dan menampilkan. Pada Chapter ini kita akan membuat fungsi mengirim hasil perhitungan kembali ke pemanggil melalui return value.
Fungsi yang Mengembalikan Nilai #
Untuk mendeklarasikan bahwa fungsi menghasilkan nilai, kita menambahkan tanda panah -> diikuti tipe kembalian (return type) setelah tanda kurung parameter. Berikut fungsinya dalam aksi:
fn hitung_diskon(total: i64) -> i64 {
total / 10
}
fn main() {
let total_belanja = 500000;
let diskon = hitung_diskon(total_belanja);
let bayar = total_belanja - diskon;
println!("Total belanja: Rp{}", total_belanja);
println!("Diskon: Rp{}", diskon);
println!("Yang harus dibayar: Rp{}", bayar);
}Output yang Dihasilkan:
Total belanja: Rp500000
Diskon: Rp50000
Yang harus dibayar: Rp450000Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1 (
fn hitung_diskon(total: i64) -> i64 {): Deklarasi menyatakan bahwa fungsi menerimai64dan mengembalikani64. Kompiler kini punya janji yang wajib ditepati oleh tubuh fungsi. - Baris 2 (
total / 10): Baris terakhir tubuh fungsi ditulis tanpa titik koma. Ekspresi ini menjadi nilai kembalian fungsi. - Baris 7 (
let diskon = hitung_diskon(total_belanja);): Pemanggilanhitung_diskon(500000)dievaluasi menjadi nilai50000, lalu nilai tersebut diikat ke variabeldiskon. Pemanggilan fungsi bernilai kembali dapat dipakai persis seperti angka biasa. - Baris 8 (
let bayar = total_belanja - diskon;): Nilai hasil fungsi ikut berpartisipasi dalam perhitungan berikutnya — inilah kekuatan pola hitung-pisah-dari-tampilkan.
Fungsi tanpa -> (seperti sapa pada Chapter sebelumnya) secara implisit mengembalikan tipe kosong (). Karena itu fungsi pencetak cocok dipanggil sebagai statement, sedangkan fungsi bernilai kembali paling alami dipanggil sebagai bagian dari ekspresi.
Return Implisit dari Ekspresi Terakhir #
Rust punya kebiasaan yang membedakannya dari banyak bahasa: ekspresi terakhir pada tubuh fungsi otomatis menjadi nilai kembalian, tanpa perlu menulis kata return dan tanpa titik koma. Gaya ini disebut implicit return dan merupakan idiom dominan dalam kode Rust sehari-hari. Tubuh fungsi dibaca layaknya rumus matematika: hitung_diskon adalah total / 10.
Perbandingannya begini — dua fungsi berikut identik secara perilaku:
fn versi_idiomatik(total: i64) -> i64 {
total / 10
}
fn versi_eksplisit(total: i64) -> i64 {
return total / 10;
}Keduanya sah dan hasilnya sama, tetapi komunitas Rust memilih versi idiomatik untuk baris terakhir fungsi. Kata kunci return disimpan untuk kebutuhan yang berbeda, yaitu pengembalian di tengah jalan.
Kata Kunci return untuk Pengembalian Awal #
Kata kunci return berkilau justru saat fungsi perlu berhenti lebih cepat dari baris terakhirnya. Kasus klasiknya adalah pengecekan khusus yang kalau terpenuhi membuat sisa perhitungan tidak relevan:
fn harga_final(harga: i64, member: bool) -> i64 {
if member {
return harga - (harga / 20);
}
harga
}
fn main() {
println!("Harga member: Rp{}", harga_final(100000, true));
println!("Harga biasa: Rp{}", harga_final(100000, false));
}Output yang Dihasilkan:
Harga member: Rp95000
Harga biasa: Rp100000Mari kita telaah alur kedua pemanggilan pada kode di atas:
- Baris 2–4 (
if member { return ...; }): Ketika argumentmemberbernilaitrue, blok if berjalan danreturnsegera mengirimharga - (harga / 20)kembali ke pemanggil. Baris-baris setelah blok if sama sekali tidak dieksekusi. - Baris 6 (
harga): Ketikamemberbernilaifalse, blok if dilewati, eksekusi sampai ke baris terakhir, dan nilaihargamenjadi nilai kembalian lewat mekanisme implicit return. - Baris 10 (
harga_final(100000, true)): Pemanggilan dengantrueberhenti di cabang member dan menghasilkan95000. - Baris 11 (
harga_final(100000, false)): Pemanggilan denganfalsemelewati cabang member dan menghasilkan100000dari baris terakhir.
Pola “periksa kasus khusus, return lebih awal, sisakan jalur normal di akhir” membuat fungsi tetap datar dan mudah dibaca, tanpa menumpuk logika utama jauh di dalam lekukan else.
Memakai Hasil Fungsi Langsung dalam Ekspresi #
Karena pemanggilan fungsi bernilai kembali adalah expression, hasilnya tidak wajib disimpan ke variabel terlebih dahulu. Hasil pemanggilan dapat langsung dicetak atau diikutkan dalam hitungan:
fn hitung_diskon(total: i64) -> i64 {
total / 10
}
fn main() {
let total_belanja = 500000;
println!("Diskon: Rp{}", hitung_diskon(total_belanja));
println!("Bayar: Rp{}", total_belanja - hitung_diskon(total_belanja));
}Output yang Dihasilkan:
Diskon: Rp50000
Bayar: Rp450000Mari kita telaah kedua pemanggilan pada kode di atas:
- Baris 8 (
println!("Diskon: Rp{}", hitung_diskon(total_belanja));): Pemanggilanhitung_diskon(500000)dievaluasi menjadi50000lebih dulu, kemudian nilai tersebut mengisi{}pada teks yang dicetak. - Baris 9 (
total_belanja - hitung_diskon(total_belanja)): Pemanggilan kedua dievaluasi menjadi50000, lalu hasilnya menjadi operan pengurangan bersamatotal_belanja, sehingga tercetak450000.
Perhatikan bahwa fungsi dihitung dua kali pada contoh di atas. Untuk perhitungan ringan hal ini tidak masalah, tetapi kalau fungsi melakukan pekerjaan berat, lebih hemat memanggilnya sekali, menyimpan hasilnya ke variabel seperti contoh sebelumnya, lalu memakai variabel tersebut berkali-kali.
Jebakan Titik Koma pada Baris Terakhir #
Implicit return punya konsekuensi yang sering menjegal pemula: titik koma mengubah segalanya. Ekspresi total / 10 menghasilkan angka, tetapi total / 10; adalah statement yang membuang hasilnya dan menghasilkan nilai kosong (). Perhatikan kode berikut:
fn hitung_diskon(total: i64) -> i64 {
total / 10;
}
fn main() {
println!("{}", hitung_diskon(500000));
}Kompilasi gagal dan kompiler menampilkan:
error[E0308]: mismatched types
--> src/main.rs:1:33
|
1 | fn hitung_diskon(total: i64) -> i64 {
| ------------- ^^^ expected `i64`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | total / 10;
| - help: remove this semicolon to return this valuePerhatikan betapa membantunya kompiler Rust: pesan galat menjelaskan bahwa tubuh fungsi mengembalikan () secara implisit, dan bahkan menyarankan menghapus titik koma pada baris total / 10; sebagai perbaikan. Galat satu karakter ini akan menjadi teman lama kita saat menulis fungsi Rust, jadi kenali bentuknya sejak dini.
Ringkasan #
Tipe kembalian dideklarasikan dengan -> Tipe dan menjadi janji yang diperiksa kompiler. Ekspresi terakhir tanpa titik koma otomatis menjadi nilai kembalian, sedangkan return ekspresi; dipakai untuk berhenti lebih awal dari tengah fungsi. Nilai hasil pemanggilan dapat diikat ke variabel dan dipakai dalam perhitungan lanjutan, memungkinkan kita memisahkan fungsi penghitung dari fungsi penampil. Sebelum melangkah lebih jauh, kita perlu mempertajam satu istilah yang sudah beberapa kali muncul: apa sebenarnya beda expression dan statement? Chapter berikutnya menjawabnya.