Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kapanışlar (Closures)

Rust’taki kapanışlar, bir değişkende saklayabileceğiniz veya diğer fonksiyonlara argüman olarak aktarabileceğiniz anonim fonksiyonlardır (anonymous functions). Kapanışı tek bir yerde oluşturabilir ve daha sonra kapanışı farklı bir bağlamda (context) değerlendirmek üzere başka bir yerde çağırabilirsiniz. Fonksiyonların aksine, kapanışlar tanımlandıkları kapsamdaki değerleri yakalayabilirler (capture). Bu kapanış özelliklerinin kodun yeniden kullanımına (code reuse) ve davranışların özelleştirilmesine (behavior customization) nasıl olanak tanıdığını göstereceğiz.

Çevreyi Yakalamak (Capturing the Environment)

İlk olarak, daha sonra kullanmak üzere tanımlandıkları çevredeki değerleri yakalamak için kapanışları nasıl kullanabileceğimizi inceleyeceğiz. Senaryomuz şu: Tişört şirketimiz arada bir, promosyon olarak e-posta listemizdeki (mailing list) birine özel, sınırlı sayıda üretilmiş bir gömlek (shirt) hediye ediyor. E-posta listesindeki kişiler isteğe bağlı olarak profillerine favori renklerini ekleyebilirler. Ücretsiz gömlek için seçilen kişinin favori rengi ayarlanmışsa o renk gömleği alır. Kişi favori bir renk belirtmemişse şirkette şu anda en çok hangi renk varsa onu alır.

Bunu uygulamanın pek çok yolu vardır. Bu örnek için, Kirmizi ve Mavi varyantlarına (seçeneklerine) sahip GomlekRengi adlı bir enum kullanacağız (basitlik adına mevcut renk sayısını sınırlıyoruz). Şirketin envanterini, şu anda stokta bulunan gömlek renklerini temsil eden bir Vec<GomlekRengi> barındıran gomlekler adlı bir alana (field) sahip Envanter struct’ı ile temsil ediyoruz. Envanter üzerinde tanımlanan hediye_et metodu, ücretsiz gömlek kazananın isteğe bağlı (optional) gömlek rengi tercihini alır ve kişinin alacağı gömlek rengini döndürür. Bu kurulum Liste 13-1’de gösterilmiştir.

Filename: src/main.rs
#[derive(Debug, PartialEq, Copy, Clone)]
enum GomlekRengi {
    Kirmizi,
    Mavi,
}

struct Envanter {
    gomlekler: Vec<GomlekRengi>,
}

impl Envanter {
    fn hediye_et(&self, kullanici_tercihi: Option<GomlekRengi>) -> GomlekRengi {
        kullanici_tercihi.unwrap_or_else(|| self.en_cok_stoklanan())
    }

    fn en_cok_stoklanan(&self) -> GomlekRengi {
        let mut num_red = 0;
        let mut num_blue = 0;

        for color in &self.gomlekler {
            match color {
                GomlekRengi::Kirmizi => num_red += 1,
                GomlekRengi::Mavi => num_blue += 1,
            }
        }
        if num_red > num_blue {
            GomlekRengi::Kirmizi
        } else {
            GomlekRengi::Mavi
        }
    }
}

fn main() {
    let magaza = Envanter {
        gomlekler: vec![GomlekRengi::Mavi, GomlekRengi::Kirmizi, GomlekRengi::Mavi],
    };

    let kullanici_tercihi1 = Some(GomlekRengi::Kirmizi);
    let giveaway1 = magaza.hediye_et(kullanici_tercihi1);
    println!(
        "{:?} tercihine sahip kullanıcı {:?} alıyor",
        kullanici_tercihi1, giveaway1
    );

    let kullanici_tercihi2 = None;
    let giveaway2 = magaza.hediye_et(kullanici_tercihi2);
    println!(
        "{:?} tercihine sahip kullanıcı {:?} alıyor",
        kullanici_tercihi2, giveaway2
    );
}
Listing 13-1: Gömlek şirketinin hediye etme senaryosu

main fonksiyonunda tanımlanan magaza, bu sınırlı üretim promosyonu için dağıtılmak üzere kalan iki mavi gömleğe ve bir kırmızı gömleğe sahiptir. Kırmızı gömlek tercih eden bir kullanıcı için ve tercihi olmayan bir kullanıcı için hediye_et metodunu çağırıyoruz.

Yine, bu kod birçok şekilde uygulanabilirdi ve burada kapanışlara odaklanmak için, kapanış kullanan hediye_et metodunun gövdesi hariç olmak üzere daha önce öğrendiğiniz kavramlara bağlı kaldık. hediye_et metodunda, kullanıcı tercihini Option<GomlekRengi> türünde bir parametre olarak alıyoruz ve kullanici_tercihi üzerinde unwrap_or_else metodunu çağırıyoruz. Option<T> üzerindeki unwrap_or_else metodu standart kütüphane tarafından tanımlanır. Bir argüman alır: herhangi bir argümanı olmayan ve T değerini (bu durumda GomlekRengi olmak üzere Option<T>’nin Some varyantında saklanan aynı tür) döndüren bir kapanış. Option<T> Some varyantı ise unwrap_or_else Some içinden değeri döndürür. Option<T> None varyantı ise unwrap_or_else kapanışı çağırır ve kapanış tarafından döndürülen değeri döndürür.

unwrap_or_else’e argüman olarak || self.en_cok_stoklanan() (|| self.most_stocked()) kapanış ifadesini belirtiyoruz. Bu, kendi başına hiçbir parametre almayan bir kapanıştır (kapanışın parametreleri olsaydı, iki dikey çubuk arasında görünüverirdi). Kapanışın gövdesi self.en_cok_stoklanan()’ı çağırır. Kapanışı burada tanımlıyoruz ve unwrap_or_else’in uygulaması sonuca ihtiyaç duyulursa kapanışı daha sonra değerlendirecektir.

Bu kodu çalıştırmak aşağıdakini yazdırır:

$ cargo run
   Compiling gomlek-sirketi v0.1.0 (file:///projects/gomlek-sirketi)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/gomlek-sirketi`
The user with preference Some(Kirmizi) gets Kirmizi
The user with preference None gets Mavi

Buradaki ilginç bir özellik, mevcut Envanter örneği üzerinde self.en_cok_stoklanan() öğesini çağıran bir kapanış aktarmış olmamızdır. Standart kütüphanenin, tanımladığımız Envanter veya GomlekRengi türleri ya da bu senaryoda kullanmak istediğimiz mantık hakkında hiçbir şey bilmesine gerek yoktu. Kapanış, self Envanter örneğine yönelik değiştirilemez bir referans (immutable reference) yakalar (captures) ve bunu unwrap_or_else metoduna belirlediğimiz kod ile iletir. Fonksiyonlar ise çevrelerini bu şekilde yakalayamazlar.

Kapanış Türlerini Çıkarsamak ve Açıklamak (Inferring and Annotating)

Fonksiyonlar ve kapanışlar arasında daha fazla fark vardır. Kapanışlar genellikle, fn fonksiyonlarının yaptığı gibi parametrelerin veya dönüş değerinin türlerini açıklamanızı gerektirmez. Türler, kullanıcılarınıza sunulan açık bir arayüzün (interface) parçası olduğu için fonksiyonlarda tür açıklamaları (type annotations) gereklidir. Bu arayüzü katı bir şekilde (rigidly) tanımlamak, bir fonksiyonun hangi tür değerleri kullandığı ve döndürdüğü konusunda herkesin hemfikir olmasını sağlamak açısından önemlidir. Kapanışlar ise bunun gibi açık bir arayüzde kullanılmazlar: Değişkenlerde saklanırlar ve onlara isim vermeden ve onları kütüphanemizin kullanıcılarına açmadan (exposing) kullanılırlar.

Kapanışlar tipik olarak kısadır ve rastgele bir senaryodan ziyade yalnızca dar bir bağlamda geçerlidir. Bu sınırlı bağlamlar (limited contexts) içinde derleyici (compiler), çoğu değişkenin türünü çıkarsayabildiği gibi, parametrelerin türlerini ve dönüş türünü de çıkarsayabilir (derleyicinin kapanış türü açıklamalarına da ihtiyaç duyduğu nadir durumlar vardır).

Değişkenlerde olduğu gibi, eğer kesinlikle gerekenden (strictly necessary) daha uzun, ayrıntılı (verbose) olma pahasına açıklığı (explicitness) ve netliği (clarity) artırmak istersek tür açıklamaları ekleyebiliriz. Bir kapanış için türleri açıklamak, Liste 13-2’de gösterilen tanıma benzeyecektir. Bu örnekte, Liste 13-1’de yaptığımız gibi onu argüman olarak ilettiğimiz yerde tanımlamak yerine bir kapanış tanımlıyor ve onu bir değişkende saklıyoruz.

Filename: src/main.rs
use std::thread;
use std::time::Duration;

fn generate_workout(intensity: u32, random_number: u32) {
    let expensive_closure = |num: u32| -> u32 {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_closure(intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}
Listing 13-2: Kapanışa parametre ve dönüş değeri türlerinin isteğe bağlı tür açıklamalarını eklemek

Tür açıklamaları eklendiğinde, kapanışların sözdizimi fonksiyonların sözdizimine daha çok benzer görünür. Karşılaştırma için burada, parametresine 1 ekleyen bir fonksiyon ve aynı davranışa sahip bir kapanış tanımlıyoruz. İlgili kısımları hizalamak için bazı boşluklar ekledik. Bu, dikey çubukların (pipes) kullanımı ve isteğe bağlı olan sözdizimi miktarı haricinde kapanış sözdiziminin fonksiyon sözdizimine ne kadar benzediğini göstermektedir:

fn  bir_ekle_v1   (x: u32) -> u32 { x + 1 }
let bir_ekle_v2 = |x: u32| -> u32 { x + 1 };
let bir_ekle_v3 = |x|             { x + 1 };
let bir_ekle_v4 = |x|               x + 1  ;

İlk satır bir fonksiyon tanımını ve ikinci satır tamamen açıklanmış (annotated) bir kapanış tanımını gösterir. Üçüncü satırda, kapanış tanımından tür açıklamalarını kaldırıyoruz. Dördüncü satırda, kapanış gövdesinde sadece bir ifade olduğu için isteğe bağlı (optional) olan süslü parantezleri (brackets) kaldırıyoruz. Bunların tümü, çağrıldıklarında aynı davranışı üretecek geçerli (valid) tanımlamalardır. bir_ekle_v3 ve bir_ekle_v4 satırları derlenebilmek için kapanışların değerlendirilmesini gerektirir, çünkü türler kullanımlarından çıkarılacaktır (inferred). Bu, Rust’ın türü çıkarabilmesi için tür açıklamalarına veya Vec’e eklenecek bir türden değerlere ihtiyaç duyan let v = Vec::new(); komutuna benzer.

Kapanış tanımlarında derleyici her bir parametre ve dönüş değeri için somut (concrete) bir tür çıkaracaktır. Örneğin, Liste 13-3 sadece parametre olarak aldığı değeri döndüren kısa bir kapanışın tanımını göstermektedir. Bu kapanış, bu örneğin amacı dışında pek kullanışlı değildir. Tanıma herhangi bir tür açıklaması (type annotations) eklemediğimize dikkat edin. Herhangi bir tür açıklaması olmadığı için kapanışı herhangi bir türle çağırabiliriz ki bunu ilk defa burada String ile yaptık. Daha sonra ornek_kapanis’ı bir tamsayı ile çağırmaya çalışırsak hata alırız.

Filename: src/main.rs
fn main() {
    let ornek_kapanis = |x| x;

    let s = ornek_kapanis(String::from("merhaba"));
    let n = ornek_kapanis(5);
}
Listing 13-3: Türleri iki farklı türle çıkarsanan (inferred) bir kapanışı çağırmaya çalışmak

Derleyici bize şu hatayı verir:

$ cargo run
   Compiling kapanis-ornek v0.1.0 (file:///projects/kapanis-ornek)
error[E0308]: mismatched types
 --> src/main.rs:5:29
  |
5 |     let n = ornek_kapanis(5);
  |             --------------- ^ expected `String`, found integer
  |             |
  |             arguments to this function are incorrect
  |
note: expected because the closure was earlier called with an argument of type `String`
 --> src/main.rs:4:29
  |
4 |     let s = ornek_kapanis(String::from("merhaba"));
  |             --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String`
  |             |
  |             in this closure call
note: closure parameter defined here
 --> src/main.rs:2:28
  |
2 |     let ornek_kapanis = |x| x;
  |                            ^
help: try using a conversion method
  |
5 |     let n = ornek_kapanis(5.to_string());
  |                              ++++++++++++

For more information about this error, try `rustc --explain E0308`.
error: could not compile `kapanis-ornek` (bin "kapanis-ornek") due to 1 previous error

ornek_kapanis’ı String değeri ile ilk kez çağırdığımızda derleyici x’in türünü ve kapanışın dönüş türünü String olarak çıkarsar (infers). Bu türler daha sonra ornek_kapanis içindeki kapanışa kilitlenir (locked into) ve aynı kapanışla başka bir tür kullanmaya çalıştığımızda tür hatası alırız.

Referansları Yakalamak (Capturing) veya Sahipliği (Ownership) Taşımak

Kapanışlar, bir fonksiyonun bir parametreyi alabildiği üç yolla doğrudan eşleşen üç yolla değerleri ortamlarından yakalayabilirler: değiştirilemez şekilde ödünç alma (borrowing immutably), değiştirilebilir şekilde ödünç alma (borrowing mutably) ve sahipliği alma (taking ownership). Kapanışın, fonksiyonun gövdesinin yakalanan değerlerle ne yaptığına dayanarak bunlardan hangisini kullanacağına kendisi karar verecektir.

Liste 13-4’te, sadece değeri yazdırmak için değiştirilemez bir referansa ihtiyaç duyduğundan dolayı liste adlı vektöre değiştirilemez bir referans yakalayan bir kapanış tanımlıyoruz.

Filename: src/main.rs
fn main() {
    let liste = vec![1, 2, 3];
    println!("Before defining closure: {liste:?}");

    let sadece_odunc_alir = || println!("From closure: {liste:?}");

    println!("Before calling closure: {liste:?}");
    sadece_odunc_alir();
    println!("After calling closure: {liste:?}");
}
Listing 13-4: Değiştirilemez bir referans yakalayan bir kapanışı tanımlamak ve çağırmak

Bu örnek ayrıca bir değişkenin bir kapanış tanımına bağlanabileceğini ve daha sonra tıpkı değişken adı bir fonksiyon adıymış gibi değişken adını ve parantezleri kullanarak kapanışı çağırabileceğimizi gösterir.

Aynı anda liste’ye birden çok değiştirilemez referans alabileceğimiz için, liste’ye kapanış tanımından önceki, kapanış tanımından sonraki ama kapanış çağrılmadan önceki ve kapanış çağrıldıktan sonraki kodlardan hâlâ erişilebilir. Bu kod derlenir, çalışır ve şunu yazdırır:

$ cargo run
   Compiling kapanis-ornek v0.1.0 (file:///projects/kapanis-ornek)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/kapanis-ornek`
Kapanışı tanımlamadan önce: [1, 2, 3]
Kapanışı çağırmadan önce: [1, 2, 3]
From closure: [1, 2, 3]
Kapanışı çağırdıktan sonra: [1, 2, 3]

Sonraki adımda, Liste 13-5’te, liste vektörüne bir öğe eklemesi için kapanış gövdesini (closure body) değiştiriyoruz. Kapanış artık değiştirilebilir bir referans yakalar (captures).

Filename: src/main.rs
fn main() {
    let mut liste = vec![1, 2, 3];
    println!("Before defining closure: {liste:?}");

    let mut degistirilebilir_odunc_alir = || liste.push(7);

    degistirilebilir_odunc_alir();
    println!("After calling closure: {liste:?}");
}
Listing 13-5: Değiştirilebilir bir referans yakalayan bir kapanışı tanımlamak ve çağırmak

Bu kod derlenir, çalışır ve şunu yazdırır:

$ cargo run
   Compiling kapanis-ornek v0.1.0 (file:///projects/kapanis-ornek)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/kapanis-ornek`
Kapanışı tanımlamadan önce: [1, 2, 3]
Kapanışı çağırdıktan sonra: [1, 2, 3, 7]

degistirilebilir_odunc_alir kapanışının tanımlanması ile çağrılması arasında artık bir println! bulunmadığına dikkat edin: degistirilebilir_odunc_alir tanımlandığında, liste’ye değiştirilebilir bir referans yakalar. Kapanış çağrıldıktan sonra kapanışı bir daha kullanmayız, bu nedenle değiştirilebilir ödünç alma (mutable borrow) sona erer. Kapanış tanımı ile kapanış çağrısı arasında yazdırmak (print) için değiştirilemez bir ödünç almaya (immutable borrow) izin verilmez, çünkü değiştirilebilir bir ödünç alma varken başka hiçbir ödünç almaya izin verilmez. Hangi hata mesajını aldığınızı görmek için oraya bir println! eklemeyi deneyin!

Kapanışın gövdesi sahipliğe kesin olarak (strictly) ihtiyaç duymasa bile kapanışın kullandığı değerlerin sahipliğini çevreden almasını zorlamak (force) istiyorsanız parametre listesinden önce move (taşı) anahtar kelimesini kullanabilirsiniz.

Bu teknik çoğunlukla verileri taşımak (move) için yeni bir iş parçacığına bir kapanış aktarılırken, verilerin yeni iş parçacığı tarafından sahiplenilmesi için kullanışlıdır. Eşzamanlılıktan (concurrency) bahsederken 16. Bölümde iş parçacıklarını ve bunları neden kullanmak isteyeceğinizi ayrıntılı olarak tartışacağız, ancak şimdilik move anahtar kelimesine ihtiyaç duyan bir kapanış kullanarak yeni bir iş parçacığı oluşturmayı (spawning) kısaca inceleyelim. Liste 13-6, vektörü main (ana) iş parçacığı yerine yeni bir iş parçacığında yazdırmak için Liste 13-4’ün değiştirilmiş halini göstermektedir.

Filename: src/main.rs
use std::thread;

fn main() {
    let liste = vec![1, 2, 3];
    println!("Before defining closure: {liste:?}");

    thread::spawn(move || println!("From thread: {liste:?}"))
        .join()
        .unwrap();
}
Listing 13-6: İş parçacığı için olan kapanışı liste’nin sahipliğini almaya zorlamak için move kullanmak

Yeni bir iş parçacığı yaratıyoruz (spawn) ve bu iş parçacığına argüman olarak çalıştırması için bir kapanış veriyoruz. Kapanış gövdesi (closure body) listeyi yazdırır. Liste 13-4’te kapanış, yazdırabilmek için gerek duyduğu en az (least amount of access) erişim hakkı olduğundan dolayı liste’yi sadece değiştirilemez bir referans kullanarak yakalamıştı. Bu örnekte, kapanış gövdesinin hala yalnızca değiştirilemez bir referansa ihtiyacı olsa da kapanış tanımının başına move anahtar kelimesini koyarak liste’nin kapanışa taşınması (moved) gerektiğini belirtmeliyiz. Eğer ana (main) iş parçacığı yeni iş parçacığı üzerinde join (katıl) çağrısı yapmadan önce daha fazla işlem gerçekleştirirse, yeni iş parçacığı ana iş parçacığının geri kalanı bitmeden önce bitebilir ya da ana iş parçacığı önce bitebilir. Ana iş parçacığı liste’nin sahipliğini sürdürürse ancak yeni iş parçacığından önce bitip liste’yi düşürürse, iş parçacığındaki değiştirilemez referans (immutable reference) geçersiz olur. Bu nedenle derleyici, referansın geçerli (valid) olması için yeni iş parçacığına verilen kapanışa liste’nin taşınmasını gerektirir. Hangi derleyici hatalarını (compiler errors) aldığınızı görmek için move anahtar kelimesini kaldırmayı veya kapanış tanımlandıktan sonra ana iş parçacığında liste’yi kullanmayı deneyin!

Yakalanan Değerleri Kapanışların Dışına Taşımak (Moving)

Bir kapanış tanımlandığı çevreden bir değerin referansını veya sahipliğini yakaladığında (böylece, eğer varsa, nelerin kapanışın içine taşındığını etkiler), kapanışın gövdesindeki kod, kapanış daha sonra değerlendirildiğinde referanslara veya değerlere ne olacağını tanımlar (böylece, eğer varsa, nelerin kapanışın dışına taşındığını etkiler).

Bir kapanış gövdesi aşağıdakilerden herhangi birini yapabilir: Yakalanan (captured) değeri kapanıştan dışarı taşıyabilir, yakalanan değeri değiştirebilir (mutate), değeri ne taşıyabilir ne de değiştirebilir veya başından itibaren ortamdan hiçbir şey yakalamayabilir.

Bir kapanışın ortamdaki değerleri yakalama ve ele alma şekli (handles values), kapanışın hangi trait’leri uyguladığını etkiler ve trait’ler, fonksiyonların ve struct’ların ne tür kapanışları kullanabileceklerini nasıl belirttiklerini gösterir. Kapanışlar, kapanış gövdesinin değerleri nasıl ele aldığına bağlı olarak bu üç Fn trait’inden birini, ikisini veya üçünü birden eklemeli bir biçimde otomatik olarak uygular:

  • FnOnce bir kez çağrılabilen kapanışlar için geçerlidir. Tüm kapanışlar en azından bu trait’i uygular çünkü tüm kapanışlar çağrılabilir. Yakaladığı değerleri kendi gövdesinin dışına çıkaran bir kapanış sadece FnOnce’ı uygulayacak ve diğer Fn trait’lerini uygulamayacaktır çünkü sadece bir kez çağrılabilir.
  • FnMut yakaladıkları değerleri kendi gövdelerinin dışına çıkarmayan ancak yakalanan değerleri değiştirebilen (mutate) kapanışlar için geçerlidir. Bu kapanışlar birden fazla kez çağrılabilir.
  • Fn çevrelerinden hiçbir şey yakalamayan kapanışların yanı sıra, yakaladıkları değerleri kendi gövdelerinden dışarı çıkarmayan ve yakalanan değerleri değiştirmeyen kapanışlar için geçerlidir. Bu kapanışlar ortamlarını değiştirmeden birden fazla kez çağrılabilir ki bu, bir kapanışın aynı anda birden fazla kez çağrılması gibi durumlarda önemlidir.

Liste 13-1’de kullandığımız Option<T> üzerindeki unwrap_or_else metodunun tanımına bakalım:

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

Hatırlayacağınız gibi T, bir Option’ın Some varyantındaki değerin türünü temsil eden jenerik türdür. Bu T türü aynı zamanda unwrap_or_else fonksiyonunun dönüş türüdür: Örneğin Option<String> üzerinde unwrap_or_else çağıran kod bir String alacaktır.

Daha sonra, unwrap_or_else fonksiyonunun ek jenerik F tür parametresine sahip olduğuna dikkat edin. F türü, unwrap_or_else çağrılırken sağladığımız kapanış olan f adlı parametrenin türüdür.

Jenerik F türü üzerinde belirtilen trait sınırı (trait bound) FnOnce() -> T’dir; bu da F’nin bir kez çağrılabilmesi, hiç argüman almaması ve bir T döndürmesi gerektiği anlamına gelir. Trait sınırında FnOnce kullanmak, unwrap_or_else’in f’yi bir kereden fazla çağırmayacağı kısıtlamasını (constraint) ifade eder. unwrap_or_else’in gövdesinde Option’ın Some olması halinde f’nin çağrılmayacağını görebiliriz. Eğer Option None ise f bir kez çağrılacaktır. Tüm kapanışlar FnOnce’ı uyguladığı için, unwrap_or_else her üç tür kapanışı da kabul eder ve olabildiğince esnektir.

Not: Yapmak istediğimiz şey ortamdan bir değer yakalamayı gerektirmiyorsa Fn traitlerinden birini uygulayan bir şeye ihtiyaç duyduğumuz yerde bir kapanış yerine bir fonksiyonun adını kullanabiliriz. Örneğin, Option<Vec<T>> değeri üzerinde değerin None olması durumunda yeni ve boş bir vektör elde etmek için unwrap_or_else(Vec::new) çağırabiliriz. Derleyici (compiler), bir fonksiyon tanımı için Fn traitlerinden hangisi geçerliyse onu otomatik olarak uygular.

Şimdi bunun unwrap_or_else’den nasıl farklı olduğunu ve sort_by_key’in neden trait sınırı için FnOnce yerine FnMut kullandığını görmek üzere dilimler üzerinde tanımlanan standart kütüphane metodu sort_by_key’e (anahtara_göre_sırala) bakalım. Kapanış, değerlendirilen dilimdeki mevcut ögeye bir referans formunda tek bir argüman alır ve sıralanabilen (ordered) K türünde bir değer döndürür. Bu fonksiyon, bir dilimi her bir ögenin belirli bir özniteliğine (attribute) göre sıralamak istediğinizde kullanışlıdır. Liste 13-7’de Dikdortgen örneklerinden oluşan bir listemiz var ve bunları genislik özniteliklerine göre düşükten yükseğe sıralamak için sort_by_key kullanıyoruz.

Filename: src/main.rs
#[derive(Debug)]
struct Dikdortgen {
    genislik: u32,
    yukseklik: u32,
}

fn main() {
    let mut liste = [
        Dikdortgen { genislik: 10, yukseklik: 1 },
        Dikdortgen { genislik: 3, yukseklik: 5 },
        Dikdortgen { genislik: 7, yukseklik: 12 },
    ];

    liste.sort_by_key(|r| r.genislik);
    println!("{liste:#?}");
}
Listing 13-7: Dikdörtgenleri genişliğe göre sıralamak için sort_by_key kullanmak

Bu kod şunu yazdırır:

$ cargo run
   Compiling dikdortgenler v0.1.0 (file:///projects/dikdortgenler)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
     Running `target/debug/dikdortgenler`
[
    Dikdortgen {
        genislik: 3,
        yukseklik: 5,
    },
    Dikdortgen {
        genislik: 7,
        yukseklik: 12,
    },
    Dikdortgen {
        genislik: 10,
        yukseklik: 1,
    },
]

sort_by_key fonksiyonunun FnMut kapanışı alacak şekilde tanımlanmasının nedeni, kapanışı birden çok kez çağırmasıdır: dilimdeki her öğe için bir kez. |r| r.genislik (|r| r.width) kapanışı ortamından herhangi bir şey yakalamaz (capture), değiştirmez (mutate) veya dışarı taşımaz (move out), dolayısıyla trait sınırı gereksinimlerini karşılar.

Buna karşılık, Liste 13-8 ortamdan dışarıya bir değer taşıdığı için yalnızca FnOnce trait’ini uygulayan bir kapanış örneğini göstermektedir. Derleyici (compiler) bu kapanışı sort_by_key ile kullanmamıza izin vermeyecektir.

Filename: src/main.rs
#[derive(Debug)]
struct Dikdortgen {
    genislik: u32,
    yukseklik: u32,
}

fn main() {
    let mut liste = [
        Dikdortgen { genislik: 10, yukseklik: 1 },
        Dikdortgen { genislik: 3, yukseklik: 5 },
        Dikdortgen { genislik: 7, yukseklik: 12 },
    ];

    let mut siralama_islemleri = vec![];
    let deger = String::from("closure called");

    liste.sort_by_key(|r| {
        siralama_islemleri.push(deger);
        r.genislik
    });
    println!("{liste:#?}");
}
Listing 13-8: sort_by_key ile bir FnOnce kapanışı kullanmaya çalışmak

Bu, liste sıralanırken sort_by_key fonksiyonunun kapanışı kaç kez çağırdığını saymaya çalışmak için kurgulanmış ve dolambaçlı bir yoldur (işe yaramaz). Bu kod, bu sayma işlemini, kapanışın ortamından alınan bir String olan deger’i siralama_islemleri vektörüne iterek (pushing) yapmaya çalışır. Kapanış deger’i yakalar (captures) ve ardından deger’in sahipliğini siralama_islemleri vektörüne aktararak deger’i kapanışın dışına çıkarır. Bu kapanış bir kez çağrılabilir; ikinci kez çağırmaya çalışmak işe yaramaz, çünkü deger artık ortama yeniden siralama_islemleri’ne itilmek üzere bulunamaz! Bu nedenle, bu kapanış sadece FnOnce’ı uygular. Biz bu kodu derlemeye çalıştığımızda, kapanışın FnMut’u uygulaması gerektiğinden (must implement) dolayı deger’in kapanıştan dışarı çıkarılamayacağına (moved out) dair şu hatayı alırız:

$ cargo run
   Compiling dikdortgenler v0.1.0 (file:///projects/dikdortgenler)
error[E0507]: cannot move out of `deger`, a captured variable in an `FnMut` closure
  --> src/main.rs:18:30
   |
15 |     let deger = String::from("closure called");
   |         -----   ------------------------------ move occurs because `deger` has type `String`, which does not implement the `Copy` trait
   |         |
   |         captured outer variable
16 |
17 |     liste.sort_by_key(|r| {
   |                      --- captured by this `FnMut` closure
18 |         siralama_islemleri.push(deger);
   |                              ^^^^^ `deger` is moved here
   |
help: consider cloning the deger if the performance cost is acceptable
   |
18 |         siralama_islemleri.push(deger.clone());
   |                                   ++++++++

For more information about this error, try `rustc --explain E0507`.
error: could not compile `dikdortgenler` (bin "dikdortgenler") due to 1 previous error

Hata mesajı, kapanışın gövdesinde deger’i ortam dışına taşıyan satırı gösterir. Bunu düzeltmek için, değerleri ortam dışına taşımayacak şekilde kapanış gövdesini değiştirmeliyiz. Ortamda bir sayaç tutmak ve kapanış gövdesinde bu sayacın değerini artırmak, kapanışın kaç kez çağrıldığını saymak için çok daha anlaşılır bir yoldur. Liste 13-9’daki kapanış sort_by_key ile çalışır, çünkü siralama_islemi_sayisi sayacına sadece değiştirilebilir bir referans yakalar ve dolayısıyla birden fazla kez çağrılabilir.

Filename: src/main.rs
#[derive(Debug)]
struct Dikdortgen {
    genislik: u32,
    yukseklik: u32,
}

fn main() {
    let mut liste = [
        Dikdortgen { genislik: 10, yukseklik: 1 },
        Dikdortgen { genislik: 3, yukseklik: 5 },
        Dikdortgen { genislik: 7, yukseklik: 12 },
    ];

    let mut siralama_islemi_sayisi = 0;
    liste.sort_by_key(|r| {
        siralama_islemi_sayisi += 1;
        r.genislik
    });
    println!("{liste:#?}, sorted in {siralama_islemi_sayisi} operations");
}
Listing 13-9: sort_by_key ile bir FnMut kapanışı kullanılmasına izin verilir.

Kapanışları kullanan fonksiyonlar veya türler tanımlarken veya kullanırken Fn traitleri (özellikleri) önemlidir. Bir sonraki bölümde yineleyicileri tartışacağız. Birçok yineleyici metodu (iterator methods) kapanış argümanlarını alır, bu yüzden devam ederken bu kapanış detaylarını aklınızda bulundurun!