Portal/Notes πŸ“
Rust

Closures

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

Closure adalah konstruksi mirip fungsi yang dapat menangkap variabel dari lingkungan sekitarnya. Closure bersifat ringan dan fleksibel, memungkinkan Anda menulis kode yang ringkas yang dapat mengenkapsulasi konteks dari tempat ia didefinisikan.

Sintaks dasar closure di Rust adalah:

|parameters| expression_or_block
  • Anonymous: Closure tidak memiliki nama dan sering ditulis secara inline.
  • Environment Capturing: Closure dapat menangkap nilai dari scope di mana ia didefinisikan.
  • Traits:
    • Fn: Digunakan untuk closure yang tidak memodifikasi variabel yang ditangkap.
    • FnMut: Digunakan untuk closure yang memodifikasi variabel yang ditangkap.
    • FnOnce: Digunakan untuk closure yang mengambil kepemilikan (ownership) variabel yang ditangkap.

Contoh

Closure Dasar

fn main() {
    let add = |x, y| x + y; // Closure yang menjumlahkan dua angka
    println!("Sum: {}", add(1, 2)); // 3
}

Menangkap Environment

Closure secara inheren fleksibel dan akan melakukan apa yang diperlukan agar closure berfungsi tanpa anotasi. Ini memungkinkan penangkapan untuk secara fleksibel beradaptasi dengan use case, terkadang memindahkan (moving) dan terkadang meminjam (borrowing). Closure dapat menangkap variabel:

  • by reference: &T
  • by mutable reference: &mut T
  • by value: T

By Reference

fn main() {
    let color = String::from("green");

    // Sebuah closure untuk mencetak `color` yang segera meminjam (`&`) `color` dan
    // menyimpan pinjaman dan closure dalam variabel `print`. `color` akan tetap
    // dipinjam sampai `print` digunakan untuk terakhir kalinya.
    //
    // `println!` hanya membutuhkan argumen berupa immutable reference sehingga tidak
    // memaksakan sesuatu yang lebih restriktif.
    let print = || println!("`color`: {}", color);

    // Panggil closure menggunakan pinjaman.
    print();

    // `color` bisa dipinjam secara immutably lagi, karena closure hanya menyimpan
    // immutable reference ke `color`.
    let _reborrow = &color;
    print();

    // Move atau reborrow diizinkan setelah penggunaan terakhir `print`
    let _color_moved = color;
}

By Mutable Reference

fn main() {
    let mut count = 0;
    // Sebuah closure untuk menaikkan `count` bisa mengambil `&mut count` atau `count`
    // tetapi `&mut count` lebih tidak restriktif sehingga ia mengambil itu. Segera
    // meminjam `count`.
    //
    // `mut` diperlukan pada `inc` karena `&mut` disimpan di dalamnya. Dengan demikian,
    // memanggil closure memutasikan `count` yang memerlukan `mut`.
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // Panggil closure menggunakan mutable borrow.
    inc();

    // Closure masih meminjam `count` secara mutable karena akan dipanggil nanti.
    // Percobaan untuk reborrow akan menyebabkan error.
    // let _reborrow = &count;
    // ^ TODO: coba uncomment baris ini.
    inc();

    // Closure tidak lagi perlu meminjam `&mut count`. Oleh karena itu,
    // reborrow dapat dilakukan tanpa error.
    let _count_reborrowed = &mut count;
}
  • Jika kita uncomment baris let _reborrow = &count; kita akan mendapatkan compile error berikut karena kita sudah meminjamnya di panggilan closure di bawahnya.

    error[E0502]: cannot borrow `count` as immutable because it is also borrowed as mutable
  • Tetapi jika kita menghapus panggilan kedua ke closure, itu akan berfungsi dengan baik karena kita sudah selesai dengan closure sehingga count tidak lagi dipinjam.

By Value

fn main() {
    use std::mem;

    // Sebuah non-copy type.
    let movable = Box::new(3);

    // `mem::drop` membutuhkan `T` sehingga ini harus mengambil by value. Copy type
    // akan menyalin ke dalam closure, meninggalkan yang asli tidak tersentuh.
    // Non-copy harus berpindah dan `movable` segera pindah ke dalam closure.
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume` menghabiskan variabel sehingga ini hanya bisa dipanggil sekali.
    consume();
    // consume();
    // ^ TODO: Coba uncomment baris ini.
}
  • Jika kita uncomment panggilan kedua ke closure consume(), kita akan mendapatkan compile error di bawah karena kita memindahkan/men-drop variabel movable.

    error[E0382]: use of moved value: `consume`
  • Tetapi jika kita tidak memindahkan/men-drop variabel movable, itu akan berfungsi dengan baik.

Closure Traits

Fn

fn call_fn<F: Fn()>(f: F) {
    f();
    f(); // bisa dipanggil berkali-kali
}

fn main() {
    let x = 10;
    let print_x = || println!("x is {}", x);
    call_fn(print_x);
}
  • Menangkap variabel by reference.
  • Dapat dipanggil berkali-kali.
  • Karena FnMut dan FnOnce adalah supertraits dari Fn, instance Fn apa pun dapat digunakan sebagai parameter di mana FnMut atau FnOnce diharapkan.

FnMut

fn call_fn_mut<F: FnMut()>(mut f: F) {
    f();
    f(); // bisa dipanggil berkali-kali
}

fn main() {
    let mut x = 10;
    let mut modify_x = || x += 1; // Menangkap `x` by mutable reference
    call_fn_mut(modify_x);
    println!("x is {}", x); // 12
}
  • Menangkap variabel by mutable reference.
  • Dapat memodifikasi variabel yang ditangkap.
  • Karena FnOnce adalah supertrait dari FnMut, instance FnMut apa pun dapat digunakan di mana FnOnce diharapkan, dan karena Fn adalah subtrait dari FnMut, instance Fn apa pun dapat digunakan di mana FnMut diharapkan.

FnOnce

fn call_fn_once<F: FnOnce()>(f: F) {
    f();
    // Panggilan di bawah akan menghasilkan error: closure `FnOnce` hanya bisa dipanggil sekali
    // f();
}

fn main() {
    let x = String::from("Hello");
    let consume_x = || println!("{}", x); // Menangkap `x` by value
    call_fn_once(consume_x);
}
  • Menghabiskan variabel yang ditangkap by value.
  • Hanya bisa dipanggil sekali.
  • Karena Fn dan FnMut adalah subtraits dari FnOnce, instance Fn atau FnMut apa pun dapat digunakan di mana FnOnce diharapkan.

Best Practices dengan Closures

  • Gunakan closure untuk logika pendek dan inline:
    • Mereka sempurna untuk tugas seperti sorting, filtering, atau mapping pada collections.
  • Pilih trait yang tepat (Fn, FnMut, FnOnce):
    • Gunakan Fn untuk operasi read-only.
    • Gunakan FnMut untuk memodifikasi state.
    • Gunakan FnOnce untuk menghabiskan (consume) variabel yang ditangkap.
  • Manfaatkan type inference:
    • Hindari menentukan tipe parameter kecuali diperlukan untuk kejelasan atau disambiguasi.
  • Minimalkan environment capture:
    • Perhatikan bagaimana closure menangkap variabel, terutama dalam kode yang sensitif terhadap performa.

Referensi


Edit on GitHub

Last updated on

Closures | Faisal Affan