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 mengimplementasikanFn; diisi tipe aslinya saat kompilasi (static dispatch), tanpa alokasi heap dan tanpa vtable. Konsekuensinya: semua jalurreturnharus menghasilkan closure bertipe sama.Box<dyn Fn(...)>— trait object di heap, sama sepertiBox<dyn Trait>pada Section Polimorfisme; closure apa pun bisa dibungkusBox::newmeski 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:
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:
12 digandakan : 24
12 dilipatganda: 36Mari kita bedah baris demi baris:
- Baris 1 (
fn buat_pengganda(faktor: u64) -> impl Fn(u64) -> u64 {): tipe kembalianimpl Fn(u64) -> u64adalah janji bahwa hasil fungsi bisa dipanggil dengan satuu64dan mengembalikanu64. Tanda panah dobelnya hanya berarti tipe kembalian fungsi berisi panah tipe kembalian closure. - Baris 2 (
move |nilai| nilai * faktor): closure mengembalikannilai * faktortanpa titik koma — ekspresi terakhir jadi nilai kembalian.faktorditangkap dari parameter fungsi. - Baris 2 (kata kunci
move): parameterfaktoradalah variabel lokal yang mati begitu fungsi selesai; tanpamove, closure hanya meminjamnya — pinjaman berumur lebih pendek daripada closure yang dikembalikan, dan Rust menolaknya. Denganmove, kepemilikanfaktor(tipeu64, cukup tersalin) pindah ke struct anonim closure sehingga closure mandiri. - Baris 5–6 (
let ganda = buat_pengganda(2);dst.): dua panggilan menghasilkan dua closure terpisah, masing-masing menyimpan faktornya (2 dan 3) sendiri. - Baris 8–9 (
ganda(12)dantiga_lipat(12)): kedua closure dipanggil jauh setelahbuat_penggandaselesai 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:
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:
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:
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:
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:
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:
Selamat pagi, Budi Santoso!
Halo, Siti Rahma!Mari kita bedah:
- Baris 1 (
fn buat_sambutan(kode: u32) -> Box<dyn Fn(&str) -> String> {): tipe kembalian berganti menjadiBox<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. - Baris 3 (
Box::new(|nama| format!("Selamat pagi, {}!", nama))): closure dialokasikan di heap lalu alamatnya dibungkusBox. Kedua cabang menghasilkan closure bertipe berbeda, danBoxmenyatukan keduanya di bawah satu trait object. - Baris 10–11 (
let sapa_pagi = buat_sambutan(1);dst.): dua hasil berbeda bentuk hidup dalam tipe yang sama; pemanggilansapa_pagi("Budi Santoso")berjalan lewat vtable — dynamic dispatch. - Pemakaian
move: kedua closure ini tidak menangkap apa pun sehingga tidak butuhmove;movetetap wajar bila closure menangkap data dari parameter fungsi, sepertibuat_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.