Closures in functions
Closure memanglah powerful. Jadi bagaimana kita memasukkannya ke dalam function yang kita buat sendiri?
Anda bisa membuat function Anda sendiri yang mana ia bisa diberi input berupa closure. Tapi meletakkannya di dalam function membuatnya menjadi kurang leluasa dan Anda perlu untuk menentukan typenya. Di luar dari function closure bida menenetukan typenya sendiri di antara Fn
, FnMut
dan FnOnce
, Namun, di dalam function, Anda perlu memilih salah satunya. Cara terbaik untuk memahaminya adalah dengan melihat beberapa function signature. Di bawah ini merupakan function signature dari .all()
. Kita ingat bahwa ia memeriksa iterator untuk melihat apakah semua element bernilai true
(tergantung pada apa yang Anda putuskan, apakah true
atau false
). Bagian dari signaturenya adalah seperti ini:
fn all<F>
: ini memberi tahu Anda bahwa ada generic type F
. Closure selalu berupa generic karena ia memiliki type yang berbeda setiap saat.
(&mut self, f: F)
: &mut self
memberi tahu Anda bahwa ia adalah sebuah method. f: F
adalah apa yang biasa Anda lihat pada closure: ini adalah nama variabel dan typenya. Tentu saja, tidak ada yang spesial tentang f
dan F
, dan mereka bisa ditulis dengan nama yang berbeda. Anda bisa menulis my_closure: Closure
jika Anda menginginkannya - itu tidak masalah. Namun di dalam penulisan signature, Anda akan sering melihat f: F
.
Selanjutnya adalah bagian tentanya closurenya: F: FnMut(Self::Item) -> bool
. Di signature tersebut, dipilihlah bahwa type yang dipilih untuk closurenya adalah FnMut
, jadi ia bisa mengubah valuenya (lewat mutable reference). Ia mengubah value dari Self::Item
, yang mana itu adalah iterator yang ia ambil. Dan kembaliannya adalah true
atau false
.
Ini adalah contoh closure di dalam closure yang lebih sederhana:
Signature di atas menyatakan bahwa ia mengambil closure, yang mana closure tersebut mengambil kepemilikan dari value (FnOnce
= mengambil valuenya), dan tidak mengembalikan apapun. Jadi sekarang kita bisa memanggil closure ini, yang mana parameternya tidak mengambil apapun dan melakukan apapun yang kita mau. Kita akan membuat sebuah Vec
dan kemudian meng-iterate-nya untuk menunjukkan apa yang bisa kita lakukan sekarang.
fn do_something<F>(f: F)
where
F: FnOnce(),
{
f();
}
fn main() {
let some_vec = vec![9, 8, 10];
do_something(|| {
some_vec
.into_iter()
.for_each(|x| println!("The number is: {}", x));
})
}
Untuk contoh yang lebih nyata, kita akan membuat struct City
lagi. Untuk kali ini, struct City
memiliki lebih banyak data, yaitu tentang tahun dan populasi. Typenya adalah Vec<u32>
untuk semua tahun, dan Vec<u32>
untuk semua populasi.
City
memiliki dua function: new()
untuk membuat City
yang baru, dan .city_data()
yang mana adalah closure. Di saat kita menggunakan .city_data()
, ia memberikan kita tahunnya, populasi dan closure, sehingga kita bisa melakukan apa yang kita inginkan dengan data tersebut. Type dari closurenya adalah FnMut
sehingga kita bisa mengubah datanya. Codenya adalah seperti berikut:
#[derive(Debug)]
struct City {
name: String,
years: Vec<u32>,
populations: Vec<u32>,
}
impl City {
fn new(name: &str, years: Vec<u32>, populations: Vec<u32>) -> Self {
Self {
name: name.to_string(),
years,
populations,
}
}
fn city_data<F>(&mut self, mut f: F) // Kita gunakan self dan juga. Namun hanya f yang generic (F). f adalah closure
where
F: FnMut(&mut Vec<u32>, &mut Vec<u32>), // Closure mengambil vector dari u32 yang bersifat mutable
// yang mana keduanya itu adalah vector tahun dan populasi
{
f(&mut self.years, &mut self.populations) // Dan, ini adalah functionnya. Function tersebut mengatakan
// "gunakan closure pada self.years dan self.populations"
// Kita bisa melakukan apapun yang kita inginkan dengan menggunakan closure
}
}
fn main() {
let years = vec![
1372, 1834, 1851, 1881, 1897, 1925, 1959, 1989, 2000, 2005, 2010, 2020,
];
let populations = vec![
3_250, 15_300, 24_000, 45_900, 58_800, 119_800, 283_071, 478_974, 400_378, 401_694,
406_703, 437_619,
];
// Sekarang kira bisa membuat City
let mut tallinn = City::new("Tallinn", years, populations);
// Sekarang kita memiliki method .city_data() yang memiliki closure. Kita bisa melakukan apa yang kita mau.
// Pertama-tama, ambil data dari 5 tahun pertama yang ada di vec years dan juga population, gabung menjadi satu dan cetak.
tallinn.city_data(|city_years, city_populations| { // kita bisa menuliskan inputnya/parameternya dengan apapun yang kita mau
let new_vec = city_years
.into_iter()
.zip(city_populations.into_iter()) // satukan years dengan population menggunakan zip
.take(5) // tapi ambil hanya 5 data pertama
.collect::<Vec<(_, _)>>(); // Beritahukan Rust untuk memutuskan type yang harus digunakan untuk tuple
println!("{:?}", new_vec);
});
// sekarang, kita tambahakan data untuk tahun 2030
tallinn.city_data(|x, y| { // kali ini kita hanya menggunakan inputan/parameter x dan y
x.push(2030);
y.push(500_000);
});
// Kita tidak lagi menginginkan data di tahun 1834, sehingga kita akan menghapusnya
tallinn.city_data(|x, y| {
let position_option = x.iter().position(|x| *x == 1834);
if let Some(position) = position_option {
println!(
"Going to delete {} at position {:?} now.",
x[position], position
); // konfirmasi bahwa kita menghapus item yang tepat
x.remove(position);
y.remove(position);
}
});
println!(
"Years left are {:?}\nPopulations left are {:?}",
tallinn.years, tallinn.populations
);
}
Code di atas akan mencetak semua hasil dibawah ini, yang mana semuanya dihasilkan dari method .city_data().
:
[(1372, 3250), (1834, 15300), (1851, 24000), (1881, 45900), (1897, 58800)]
Going to delete 1834 at position 1 now.
Years left are [1372, 1851, 1881, 1897, 1925, 1959, 1989, 2000, 2005, 2010, 2020, 2030]
Populations left are [3250, 24000, 45900, 58800, 119800, 283071, 478974, 400378, 401694, 406703, 437619, 500000]