|
| 1 | +# Ders 09: Closures |
| 2 | + |
| 3 | +Closure terimi çoğu zaman anonim fonksiyon olarak da ifade edilir. Özellikle fonksiyonel dillerde yaygın olarak kullanılan closure'lar, bir değişkene atanabilir ve bu sayede fonksiyonlara parametre olarak kod bloklarının taşınması sağlanabilir. Aşağıdaki basit kod parçasında en temek haliyet bir closure tanımı yer almaktadır. |
| 4 | + |
| 5 | +```rust |
| 6 | +let square = |x| x * x; |
| 7 | +println!("{}", square(5)); |
| 8 | +``` |
| 9 | + |
| 10 | +Örnekte tanımlanan square isimli değişken x değerlerinin çarpımını hesap eden bir kod bloğunu ifade eder. square değişkenini normal bir fonksiyon gibi çağırabiliriz. Rust, built-in olarak üç önemli closure sunar. |
| 11 | + |
| 12 | +- **Fn:** Closure, dışarıdan yakaladığı değişkenleri salt okunur _(read only)_ şekilde kullanır. |
| 13 | +- **FnMut:** Closure, dış değişkenleri değiştirerek _(mutable)_ kullanabilir. |
| 14 | +- **FnOnce:** Closure, dış değişkenleri sahiplenir (move eder) ve yalnızca bir kez çağrılabilir. |
| 15 | + |
| 16 | +Özellikle nesne toplulukları üzerinden hareket eden iteratif fonksiyonları bu ön tanımlı closure'ları sıklıkla kullanır. Buradaki türler C# tarafından gelenler için delegate türüne benzetilebilir. |
| 17 | + |
| 18 | +## Filtreleme ve Sıralama İşlemleri |
| 19 | + |
| 20 | +Takip eden örnekler Game türünden veriler içeren bir vector ile ilişkilidir. Örnek oyun bilgileri için repository.rs dosyasına bakılabilir. Çok basit bir örnekle başlayalım. Oyunları yıllara göre sıralamak istediğimiz düşünelim. Normalde vector türleri belli bir key değerine göre sıralama işlemi için sort_by_key isimli metodu sağlar. Bu metod FnMut(&T) -> K türünden bir parametre kullanır. Bir başka deyişle sıralama için kullanılacak anahtar alanı ele alması gerekir. Buna göre aşağıdaki gibi bir örnek yazılabilir. |
| 21 | + |
| 22 | +```rust |
| 23 | +mod repository; |
| 24 | + |
| 25 | +use repository::*; |
| 26 | + |
| 27 | +fn year_sorter(game: &Game) -> u16 { |
| 28 | + game.year |
| 29 | +} |
| 30 | + |
| 31 | +fn print_games(games: &Vec<Game>) { |
| 32 | + for game in games { |
| 33 | + println!("{}, {}", game.year, game.title); |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +fn main() { |
| 38 | + let games = repository::load_games(); |
| 39 | + |
| 40 | + let mut games = repository::load_games(); |
| 41 | + |
| 42 | + games.sort_by_key(year_sorter); |
| 43 | + print_games(&games); |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Diğer yandan aynı işlev year_sorter fonksiyonunu yazmaya gerek kalmadan sort_by metoduna FnMut ile taşınabilecek bir kod bloğu gönderilerek de gerçekleştirilebilir. Hatta farklı sıralama ve filtreleme kritleri de aynı metodoloji ile uygulanabilir. Aşağıdaki örnek kodlarda bu durum ele alınır. |
| 48 | + |
| 49 | +```rust |
| 50 | +fn main() { |
| 51 | + let games = repository::load_games(); |
| 52 | + let mut games = repository::load_games(); |
| 53 | + |
| 54 | + // Closure ile artan yıl sıralaması |
| 55 | + games.sort_by(|g1, g2| g1.year.cmp(&g2.year)); |
| 56 | + println!("Yıla göre artan sıralama:"); |
| 57 | + print_games(&games); |
| 58 | + |
| 59 | + // Closure ile azalan yıl sıralaması |
| 60 | + games.sort_by(|g1, g2| g2.year.cmp(&g1.year)); |
| 61 | + println!("\nYıla göre azalan sıralama:"); |
| 62 | + print_games(&games); |
| 63 | + |
| 64 | + // Popülaritesi 2.0'den yüksek olan oyunlar |
| 65 | + let popular_games: Vec<Game> = games.into_iter().filter(|g| g.popularity > 2.0).collect(); |
| 66 | + println!("\nPopüler oyunlar (popularity > 2.0):"); |
| 67 | + print_games(&popular_games); |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +sort_by ve into_iter çağrısı sonrası erişilen filter metotları parametre olarak closure alır. Buna göre tüm nesen koleksiyonu üzerinde closure ifadesi ile gelen kod bloğu çalıştırılır. Örneğin popülerlik değeri 2.0 üzerinden olanları toplamak için aşağıdaki closure kullanılmıştır. |
| 72 | + |
| 73 | +```text |
| 74 | +|g| g.popularity > 2.0 |
| 75 | +``` |
| 76 | + |
| 77 | +## Metot Parametresi Olarak Closure Kullanımı |
| 78 | + |
| 79 | +Pekçok sebepten bir nesne topluluğunun çalışma zamanında neye göre filtreleneceği bilinmez. Programcı söz konusu nesne topluluğu üzerinde işletmek istediği kod bloklarını bir fonksiyon gibi geçebilmelidir. Closure ifadeleri bunu gerçekleştirmek için idealdir. Basit bir oyun sistemi tasarladığımız düşünelim. Aşağıdaki veri modellerini kullanıyoruz. |
| 80 | + |
| 81 | +```rust |
| 82 | +#[derive(Debug)] |
| 83 | +struct Player { |
| 84 | + id: u32, |
| 85 | + position: (f32, f32), |
| 86 | + velocity: (f32, f32), |
| 87 | + score: u32, |
| 88 | +} |
| 89 | + |
| 90 | +#[derive(Debug)] |
| 91 | +struct GameWorld { |
| 92 | + players: Vec<Player>, |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +Basitçe oyun sahasındaki oyuncuları tanımlayan ve nesne topluluğu olarak ele alan iki veri yapısı var. Sahadaki tüm oyuncular için farklı işlevleri işletecek farklı sistemler tasarlanabilir. Söz gelimi tüm oyuncların pozisyon bilgilerini değiştirecek tek bir fonksiyon yazılabilir veya oyunculardan belli kriterlere uyanların skorlarında değişiklik yapacak bir sistem fonksiyonu da geliştirilebilir. Burada anahtar nokta sistem fonksiyonlarının işletecekleri kodun ne olacağını bilmemeleridir. Aşağıdaki kod parçasında Fn ve FnMut trait'lerinin örnek kullanımlarını içermektedir. |
| 97 | + |
| 98 | +```rust |
| 99 | +fn update_players_system<F>(world: &mut GameWorld, mut f: F) |
| 100 | +where |
| 101 | + F: Fn(&mut Player), |
| 102 | +{ |
| 103 | + for p in &mut world.players { |
| 104 | + f(p); |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +fn update_score_system<F>(world: &GameWorld, mut f: F) |
| 109 | +where |
| 110 | + F: FnMut(&Player), |
| 111 | +{ |
| 112 | + /* |
| 113 | + Burada FnMut yerine Fn kullanıp oluşan hata mesajı incelenebilir. |
| 114 | +
|
| 115 | + error[E0594]: cannot assign to `total_team_score`, as it is a captured variable in a `Fn` closure |
| 116 | + change this to accept `FnMut` instead of `Fn` |
| 117 | + */ |
| 118 | + for p in &world.players { |
| 119 | + f(p); |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +pub fn main() { |
| 124 | + let mut world = GameWorld { |
| 125 | + players: vec![ |
| 126 | + Player { |
| 127 | + id: 1, |
| 128 | + position: (0.0, 0.0), |
| 129 | + velocity: (2.0, 0.0), |
| 130 | + score: 0, |
| 131 | + }, |
| 132 | + Player { |
| 133 | + id: 2, |
| 134 | + position: (100.0, 0.0), |
| 135 | + velocity: (8.0, 0.0), |
| 136 | + score: 0, |
| 137 | + }, |
| 138 | + ], |
| 139 | + }; |
| 140 | + |
| 141 | + let apply_gravity = |entity: &mut Player| { |
| 142 | + entity.position.0 += entity.velocity.0 * 0.9; |
| 143 | + entity.position.1 += entity.velocity.1 * 0.9; |
| 144 | + }; |
| 145 | + |
| 146 | + println!("Before Update: {:?}", world.players); |
| 147 | + update_players_system(&mut world, apply_gravity); |
| 148 | + // update_players_system(&mut world, |entity| { |
| 149 | + // entity.position.0 += entity.velocity.0 * 0.9; |
| 150 | + // entity.position.1 += entity.velocity.1 * 0.9; |
| 151 | + // }); |
| 152 | + println!("After Update: {:?}", world.players); |
| 153 | + |
| 154 | + // FnMut kullanımı ile ilgili bir örnek |
| 155 | + let mut total_team_score = 0; |
| 156 | + |
| 157 | + println!("Total score before update: {}", total_team_score); |
| 158 | + update_players_system(&mut world, |p| p.score += 2); |
| 159 | + update_score_system(&world, |p: &Player| { |
| 160 | + total_team_score += p.score; |
| 161 | + }); |
| 162 | + println!("Total score after update: {}", total_team_score); |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +Dikkat edileceği fonksiyonlar parametre olarak gelen kod bloklarının world nesnesi üzerinden ulaşılan tüm player değişkenleri için icra eder. update_player_system fonksiyonunda kullanılan f değişkeni generic bir tür olarak belirtilmiştir ve Fn trait'ini uygulaması beklenmektedir. Kısaca f yerine Fn trait'ine uygun bir closure ifadesi gelebilir. Örneğin sistem fonksiyonuna apply_gravity değişkeni ile tanımlı fonksiyonu atanabilir ya da closure ifadesi ile doğrudan bir blok gönderilebilir. update_score_system fonksiyonunda FnMut trait'ini uygulayan bir closure ifadesi beklenir. Bu örnek main fonksiyonunda yer alan total_team_score değişkeni üzerinde değişiklik yapar. FnMut olarak tanımlanmasının bir sebebi de bulunduğu scope dışındaki bir değişken üzerinde değişiklik yapacak olmasıdır. |
| 167 | + |
| 168 | +## FnOnce Senaryosu |
| 169 | + |
| 170 | +FnOnce, kullandığı değerleri sahiplenen ve bir sefer çalıştırılması istenen senaryolar için idealdir. Daha çok thread'lerin kullanıldığı durumlarda ele alınabilir. Aşağıdaki örnek kod parçasını bu anlamda ele alabiliriz. |
| 171 | + |
| 172 | +```rust |
| 173 | +fn main() |
| 174 | +{ |
| 175 | + // FnOnce Örneği |
| 176 | + let message = Some(String::from("You have unlocked a new level!")); |
| 177 | + let show_message = || { |
| 178 | + if let Some(msg) = message { |
| 179 | + println!("{}", msg); |
| 180 | + } else { |
| 181 | + println!("Message already shown."); |
| 182 | + } |
| 183 | + }; |
| 184 | + |
| 185 | + show_message(); |
| 186 | + // show_message(); // Burada 'value used here after move' hatası oluşur |
| 187 | + /* |
| 188 | + Henüz erken olsa da thread açmak FnOnce kullanımı için iyi bir örnek olabilir. |
| 189 | + thread::spawn yeni bir thread başlatırken FnOnce türünden bir closure alır. Dışarıdan |
| 190 | + değerler closure içerisine taşınır ve thread sonlanana kadar closure sahip olduğu tüm |
| 191 | + değerleri tüketir. Bu tek sefer çalıştırılması gereken bir closure olarak düşünülebilir. |
| 192 | + */ |
| 193 | + let message = String::from("Inside a thread!"); |
| 194 | + let handle = thread::spawn(move || { |
| 195 | + println!("{}", message); |
| 196 | + }); |
| 197 | + // println!("{}", message); // value borrowed here after move |
| 198 | + handle.join().unwrap(); |
| 199 | +} |
| 200 | +``` |
0 commit comments