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:

rust
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:

text
Total belanja: Rp500000
Diskon: Rp50000
Yang harus dibayar: Rp450000

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

  1. Baris 1 (fn hitung_diskon(total: i64) -> i64 {): Deklarasi menyatakan bahwa fungsi menerima i64 dan mengembalikan i64. Kompiler kini punya janji yang wajib ditepati oleh tubuh fungsi.
  2. Baris 2 (total / 10): Baris terakhir tubuh fungsi ditulis tanpa titik koma. Ekspresi ini menjadi nilai kembalian fungsi.
  3. Baris 7 (let diskon = hitung_diskon(total_belanja);): Pemanggilan hitung_diskon(500000) dievaluasi menjadi nilai 50000, lalu nilai tersebut diikat ke variabel diskon. Pemanggilan fungsi bernilai kembali dapat dipakai persis seperti angka biasa.
  4. 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:

rust
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:

rust
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:

text
Harga member: Rp95000
Harga biasa: Rp100000

Mari kita telaah alur kedua pemanggilan pada kode di atas:

  1. Baris 2–4 (if member { return ...; }): Ketika argument member bernilai true, blok if berjalan dan return segera mengirim harga - (harga / 20) kembali ke pemanggil. Baris-baris setelah blok if sama sekali tidak dieksekusi.
  2. Baris 6 (harga): Ketika member bernilai false, blok if dilewati, eksekusi sampai ke baris terakhir, dan nilai harga menjadi nilai kembalian lewat mekanisme implicit return.
  3. Baris 10 (harga_final(100000, true)): Pemanggilan dengan true berhenti di cabang member dan menghasilkan 95000.
  4. Baris 11 (harga_final(100000, false)): Pemanggilan dengan false melewati cabang member dan menghasilkan 100000 dari 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:

rust
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:

text
Diskon: Rp50000
Bayar: Rp450000

Mari kita telaah kedua pemanggilan pada kode di atas:

  1. Baris 8 (println!("Diskon: Rp{}", hitung_diskon(total_belanja));): Pemanggilan hitung_diskon(500000) dievaluasi menjadi 50000 lebih dulu, kemudian nilai tersebut mengisi {} pada teks yang dicetak.
  2. Baris 9 (total_belanja - hitung_diskon(total_belanja)): Pemanggilan kedua dievaluasi menjadi 50000, lalu hasilnya menjadi operan pengurangan bersama total_belanja, sehingga tercetak 450000.

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:

rust
fn hitung_diskon(total: i64) -> i64 {
    total / 10;
}

fn main() {
    println!("{}", hitung_diskon(500000));
}

Kompilasi gagal dan kompiler menampilkan:

text
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 value

Perhatikan 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.