Closure di Rust
Closure (fungsi anonim) adalah fungsi kecil tanpa nama yang bisa kamu simpan di variabel dan kirim ke mana-mana. Yang membuatnya berbeda dari fungsi biasa adalah closure bisa “menangkap” (capture) variabel dari scope tempat ia dibuat.
Sintaks dasar closure
Section titled “Sintaks dasar closure”fn main() { let add_one = |x: i32| x + 1; println!("{}", add_one(5));}Output:
6Garis tegak |x: i32| menggantikan tanda kurung yang dipakai fungsi biasa. Bandingkan keduanya berdampingan:
fn add_one_fn(x: i32) -> i32 { x + 1 } // fungsi biasalet add_one_closure = |x: i32| x + 1; // hal yang sama dalam bentuk closureRust sering bisa menebak tipe parameter dan tipe kembaliannya juga, jadi kamu akan sering melihat closure yang ditulis lebih pendek lagi:
let add_one = |x| x + 1;Closure tanpa parameter memakai garis tegak kosong: let say_hi = || println!("hai");, dan kamu memanggilnya dengan say_hi().
Closure beberapa baris
Section titled “Closure beberapa baris”Kalau isinya lebih dari satu ekspresi, bungkus dengan kurung kurawal seperti fungsi:
fn main() { let describe = |n: i32| { let kind = if n % 2 == 0 { "genap" } else { "ganjil" }; format!("{n} adalah bilangan {kind}") };
println!("{}", describe(7));}Output:
7 adalah bilangan ganjilformat! bekerja seperti println!, tapi alih-alih mencetak teksnya, ia mengembalikan teks itu sebagai String. Di sini, String tersebut adalah ekspresi terakhir di dalam kurung kurawal, jadi itulah yang dikembalikan closure.
Menangkap variabel dari scope di sekitarnya
Section titled “Menangkap variabel dari scope di sekitarnya”Inilah fitur yang tidak dimiliki fungsi biasa: closure bisa memakai variabel dari tempat ia didefinisikan, tanpa kamu harus mengirimkannya secara eksplisit:
fn main() { let discount = 10; let apply_discount = |price: i32| price - discount;
println!("{}", apply_discount(100)); // 90 println!("{}", apply_discount(50)); // 40}Output:
9040discount bukan parameter closure. Variabel itu hanya ada di scope main, dan closure langsung menjangkau dan memakainya. fn biasa tidak bisa melakukan ini; discount harus dikirim sebagai argumen.
Closure yang mengubah variabel tangkapannya
Section titled “Closure yang mengubah variabel tangkapannya”Closure juga bisa mengubah variabel yang ditangkapnya, asalkan variabel itu mut. Ada satu aturan tambahan: closure-nya sendiri juga harus disimpan di variabel mut. Memanggil closure itu mengubah sesuatu, dan Rust ingin hal itu terlihat di kodemu.
fn main() { let mut count = 0;
let mut increment = || { count += 1; println!("count sekarang {count}"); };
increment(); increment(); increment();
println!("count akhir: {count}");}Output:
count sekarang 1count sekarang 2count sekarang 3count akhir: 3Kalau kamu lupa menulis mut pada increment, compiler memberi tahu persis apa yang salah: calling increment requires mutable binding due to mutable borrow of count. Artinya, memanggil increment butuh variabel yang mut, karena closure itu meminjam count untuk diubah.
Ada satu hal yang perlu kamu tahu. Selama closure masih bisa dipanggil, ia punya hak untuk mengubah count, jadi tidak ada kode lain yang boleh menyentuh count. Kalau kamu menambahkan println!("{count}"); di antara dua pemanggilan increment(), kamu akan mendapat error[E0502]: cannot borrow count as immutable because it is also borrowed as mutable (tidak bisa meminjam count sebagai immutable karena sedang dipinjam sebagai mutable). Setelah pemanggilan increment() yang terakhir, tugas closure itu selesai, dan count kembali sepenuhnya milikmu. Itulah kenapa baris count akhir bisa jalan. Cerita lengkap di balik aturan ini akan kamu pelajari di halaman Borrowing.
Kadang closure perlu mengambil alih ownership (kepemilikan) penuh atas apa yang ditangkapnya, misalnya saat closure itu diberikan ke thread lain. Untuk itu, kamu menulis move sebelum garis tegaknya: move || { ... }. Kamu akan melihat contohnya di halaman Konkurensi.
Di mana closure dipakai dalam praktik
Section titled “Di mana closure dipakai dalam praktik”Kamu jarang memakai closure sendirian. Closure benar-benar berguna untuk mengirim perilaku khusus ke fungsi lain, terutama ke method iterator yang akan kamu temui di halaman Iterator:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}Output:
[2, 4, 6, 8, 10]Di sini, |n| n * 2 adalah closure yang langsung dikirim ke .map(). Pola ini (closure kecil yang menggerakkan method seperti .map() atau .filter()) adalah cara kamu benar-benar memakai closure sehari-hari.
Latihan
Section titled “Latihan”1. Harga plus pajak
Section titled “1. Harga plus pajak”Buat variabel tax_percent bernilai 20. Lalu tulis closure with_tax yang menerima harga dan mengembalikan harga ditambah pajak, memakai tax_percent yang ditangkapnya. Cetak hasilnya untuk harga 50 dan 120. Petunjuk: pajaknya price * tax_percent / 100.
Lihat solusi
fn main() { let tax_percent = 20; let with_tax = |price: i32| price + price * tax_percent / 100;
println!("50 plus pajak jadi {}", with_tax(50)); println!("120 plus pajak jadi {}", with_tax(120));}Output:
50 plus pajak jadi 60120 plus pajak jadi 144tax_percent tidak pernah dikirim sebagai argumen. Closure menangkapnya dari main, dan itulah yang tidak bisa dilakukan fn biasa.
2. Pembuat ID
Section titled “2. Pembuat ID”Tulis closure next_id yang mengembalikan 1 saat pertama kali dipanggil, 2 saat kedua kali, 3 saat ketiga kali, dan seterusnya. Panggil tiga kali dan cetak setiap ID. Petunjuk: simpan penghitung mut di luar closure, tambahkan 1 di dalamnya, dan jadikan penghitung itu ekspresi terakhir closure supaya dikembalikan.
Lihat solusi
fn main() { let mut last_id = 0;
let mut next_id = || { last_id += 1; last_id };
println!("pengguna pertama dapat id {}", next_id()); println!("pengguna kedua dapat id {}", next_id()); println!("pengguna ketiga dapat id {}", next_id());}Output:
pengguna pertama dapat id 1pengguna kedua dapat id 2pengguna ketiga dapat id 3last_id dan next_id sama-sama butuh mut: yang pertama karena nilainya berubah, yang kedua karena memanggil closure itu mengubah apa yang ditangkapnya.
Ringkasan
Section titled “Ringkasan”- Closure adalah fungsi anonim yang kecil:
|params| expression. - Tipenya sering bisa ditebak oleh Rust, jadi closure biasanya lebih pendek daripada
fnyang setara. - Tidak seperti fungsi biasa, closure bisa menangkap variabel dari scope tempat ia didefinisikan.
- Closure yang mengubah variabel tangkapannya harus disimpan di variabel
mut, dan ia meminjam variabel itu selama masih dipakai. - Closure paling berguna saat dikirim ke fungsi lain, terutama method iterator seperti
.map().
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Simbol apa yang dipakai closure untuk parameter, sebagai pengganti tanda kurung pada fungsi?
2. Apa yang bisa dilakukan closure tapi tidak bisa dilakukan fn biasa?
3. Menurut halaman ini, di mana closure paling sering dipakai dalam praktik?
4. Sebuah closure menjalankan count += 1 pada variabel yang ditangkapnya. Apa lagi yang kamu butuhkan?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Closures — The Rust Book (bahasa Inggris): bab resmi tentang closure, penebakan tipe, dan trait
Fn. - Capturing References or Moving Ownership — The Rust Book (bahasa Inggris): tiga cara closure menangkap variabel, dan kapan memakai
move. - Closures — Rust by Example (bahasa Inggris): contoh singkat yang bisa dijalankan, membandingkan closure dengan fungsi.
- Capturing — Rust by Example (bahasa Inggris): contoh lain closure yang membaca, mengubah, dan mengambil ownership variabel.
- Closure expressions — The Rust Reference (bahasa Inggris): aturan persis tentang sintaks closure dan cara menangkap variabel.