Chapter sebelumnya menyelesaikan arah masuk: fungsi menerima closure lewat bound Fn. Kini kita bahas arah keluar — fungsi yang menghasilkan closure. Pola ini disebut factory: satu fungsi membuat banyak closure serupa dengan konfigurasi berbeda, seperti pembuat aturan diskon yang menerima persentase lalu mengembalikan closure siap pakai. Masalah teknisnya masih satu keluarga — tipe closure anonim — tetapi posisi return membawa dua kesulitan baru: closure hasil harus membawa datanya sendiri karena variabel lokal fungsi mati saat fungsi selesai, dan satu tipe kembalian hanya bisa memuat satu tipe closure konkret. Rust menyiapkan dua jawaban: impl Fn untuk satu bentuk, dan Box<dyn Fn> untuk banyak bentuk.

Dua Jalan Menuliskan Tipe Kembalian #

Sama seperti pada parameter, tipe closure tidak bisa dituliskan di posisi return. Rust menyediakan dua mekanisme dari Section Traits, Polimorfisme, dan Smart Pointers, kini untuk closure:

  • impl Fn(...) — fungsi berjanji mengembalikan satu tipe konkret yang mengimplementasikan Fn; diisi tipe aslinya saat kompilasi (static dispatch), tanpa alokasi heap dan tanpa vtable. Konsekuensinya: semua jalur return harus menghasilkan closure bertipe sama.
  • Box<dyn Fn(...)> — trait object di heap, sama seperti Box<dyn Trait> pada Section Polimorfisme; closure apa pun bisa dibungkus Box::new meski tipenya berbeda, dengan biaya satu alokasi heap dan pemanggilan lewat vtable (dynamic dispatch).

impl Fn sebagai Tipe Kembalian #

Fungsi berikut menerima faktor lalu mengembalikan closure pengganda; kata kunci move wajib hadir — alasannya kita bedah setelah kode:

rust
fn buat_pengganda(faktor: u64) -> impl Fn(u64) -> u64 {
    move |nilai| nilai * faktor
}

fn main() {
    let ganda = buat_pengganda(2);
    let tiga_lipat = buat_pengganda(3);

    println!("12 digandakan  : {}", ganda(12));
    println!("12 dilipatganda: {}", tiga_lipat(12));
}

Output yang Dihasilkan:

text
12 digandakan  : 24
12 dilipatganda: 36

Mari kita bedah baris demi baris:

  1. Baris 1 (fn buat_pengganda(faktor: u64) -> impl Fn(u64) -> u64 {): tipe kembalian impl Fn(u64) -> u64 adalah janji bahwa hasil fungsi bisa dipanggil dengan satu u64 dan mengembalikan u64. Tanda panah dobelnya hanya berarti tipe kembalian fungsi berisi panah tipe kembalian closure.
  2. Baris 2 (move |nilai| nilai * faktor): closure mengembalikan nilai * faktor tanpa titik koma — ekspresi terakhir jadi nilai kembalian. faktor ditangkap dari parameter fungsi.
  3. Baris 2 (kata kunci move): parameter faktor adalah variabel lokal yang mati begitu fungsi selesai; tanpa move, closure hanya meminjamnya — pinjaman berumur lebih pendek daripada closure yang dikembalikan, dan Rust menolaknya. Dengan move, kepemilikan faktor (tipe u64, cukup tersalin) pindah ke struct anonim closure sehingga closure mandiri.
  4. Baris 5–6 (let ganda = buat_pengganda(2); dst.): dua panggilan menghasilkan dua closure terpisah, masing-masing menyimpan faktornya (2 dan 3) sendiri.
  5. Baris 8–9 (ganda(12) dan tiga_lipat(12)): kedua closure dipanggil jauh setelah buat_pengganda selesai dan masih ingat faktornya — bukti data faktor benar-benar pindah ke dalam closure.

Lupa move pada Closure yang Dikembalikan #

Mari kita buktikan klaim butir 3 dengan menghapus move — kode berikut mengembalikan closure yang meminjam variabel lokal:

rust
fn buat_pencetak() -> impl Fn() {
    let teks = String::from("Promo diskon 20%");
    || println!("{}", teks)
}

fn main() {
    let cetak = buat_pencetak();
    cetak();
}

Pesan galat asli kompilator:

text
error[E0373]: closure may outlive the current function, but it borrows `teks`, which is owned by the current function
 --> main.rs:3:5
  |
3 |     || println!("{}", teks)
  |     ^^                ---- `teks` is borrowed here
  |     |
  |     may outlive borrowed value `teks`
  |
note: closure is returned here
 --> main.rs:3:5
  |
3 |     || println!("{}", teks)
  |     ^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `teks` (and any other referenced variables), use the `move` keyword
  |
3 |     move || println!("{}", teks)
  |     ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0373`.

Pesan E0373 sama persis dengan yang kita temui di Chapter Closure dengan move, hanya kini pemicunya posisi return: closure is returned here. Closure meminjam teks milik buat_pencetak, sementara teks dijatuhkan saat fungsi berakhir — hasilnya akan memegang reference menggantung. Perbaikannya tertulis lengkap: tambahkan move agar closure memiliki teks sendiri. Inilah alasan move nyaris selalu menyertai closure yang dikembalikan.

Banyak Bentuk Kembalian: Box #

impl Fn punya satu batasan tegar: seluruh jalur return harus menghasilkan closure bertipe konkret yang sama. Padahal closure yang menangkap data berbeda otomatis bertipe berbeda. Fungsi berikut memilih antara dua closure di dua cabang if:

rust
fn buat_pengubah(faktor: u64) -> impl Fn(u64) -> u64 {
    if faktor == 0 {
        move |nilai| nilai + faktor
    } else {
        move |nilai| nilai * faktor
    }
}

fn main() {
    let ubah = buat_pengubah(3);
    println!("{}", ubah(5));
}

Pesan galat asli kompilator:

text
error[E0308]: `if` and `else` have incompatible types
 --> main.rs:5:9
  |
2 | /     if faktor == 0 {
3 | |         move |nilai| nilai + faktor
  | |         ---------------------------
  | |         |
  | |         the expected closure
  | |         expected because of this
4 | |     } else {
5 | |         move |nilai| nilai * faktor
  | |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
6 | |     }
  | |_____- `if` and `else` have incompatible types
  |
  = note: expected closure `{[email protected]:3:9: 3:21}`
             found closure `{[email protected]:5:9: 5:21}`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object
help: you could change the return type to be a boxed trait object
  |
1 - fn buat_pengubah(faktor: u64) -> impl Fn(u64) -> u64 {
1 + fn buat_pengubah(faktor: u64) -> Box<dyn Fn(u64) -> u64> {
  |
help: if you change the return type to expect trait objects, box the returned expressions
  |
3 ~         Box::new(move |nilai| nilai + faktor)
4 |     } else {
5 ~         Box::new(move |nilai| nilai * faktor)
  |

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.

Pesan E0308 memuat kalimat kunci: no two closures, even if identical, have the same type. Kedua closure di cabang if dan else sama-sama menangkap faktor tetapi tetap dua tipe anonim berbeda — sedangkan impl Fn hanya boleh memuat satu tipe. Saran help menunjukkan jalan keluarnya: ubah tipe kembalian menjadi Box<dyn Fn(u64) -> u64> dan bungkus tiap closure dengan Box::new. Kita ikuti saran itu:

rust
fn buat_sambutan(kode: u32) -> Box<dyn Fn(&str) -> String> {
    if kode == 1 {
        Box::new(|nama| format!("Selamat pagi, {}!", nama))
    } else {
        Box::new(|nama| format!("Halo, {}!", nama))
    }
}

fn main() {
    let sapa_pagi = buat_sambutan(1);
    let sapa_umum = buat_sambutan(2);

    println!("{}", sapa_pagi("Budi Santoso"));
    println!("{}", sapa_umum("Siti Rahma"));
}

Output yang Dihasilkan:

text
Selamat pagi, Budi Santoso!
Halo, Siti Rahma!

Mari kita bedah:

  1. Baris 1 (fn buat_sambutan(kode: u32) -> Box<dyn Fn(&str) -> String> {): tipe kembalian berganti menjadi Box<dyn Fn(&str) -> String> — fat pointer berisi pointer ke closure di heap dan pointer vtable, konsep dari Section Polimorfisme. Satu tipe ini sanggup menampung closure bentuk apa pun yang cocok dengan kontraknya.
  2. Baris 3 (Box::new(|nama| format!("Selamat pagi, {}!", nama))): closure dialokasikan di heap lalu alamatnya dibungkus Box. Kedua cabang menghasilkan closure bertipe berbeda, dan Box menyatukan keduanya di bawah satu trait object.
  3. Baris 10–11 (let sapa_pagi = buat_sambutan(1); dst.): dua hasil berbeda bentuk hidup dalam tipe yang sama; pemanggilan sapa_pagi("Budi Santoso") berjalan lewat vtable — dynamic dispatch.
  4. Pemakaian move: kedua closure ini tidak menangkap apa pun sehingga tidak butuh move; move tetap wajar bila closure menangkap data dari parameter fungsi, seperti buat_pengganda.

Ringkasan #

Fungsi mengembalikan closure lewat dua pintu. Pintu impl Fn menawarkan static dispatch tanpa alokasi dan menuntut move agar closure memiliki datanya sendiri (kelalaian memunculkan E0373 closure may outlive the current function) serta semua jalur return bertipe sama. Pintu Box<dyn Fn> membiarkan banyak bentuk closure berbagi satu tipe kembalian dengan biaya alokasi heap dan vtable; galat E0308 dengan catatan no two closures, even if identical, have the same type adalah penunjuk bahwa kita perlu pindah ke Box. Satu panggung terakhir menanti: closure sebagai bahan bakar utama iterator.