Gelişmiş Fonksiyonlar ve Kapanış İfadeleri

Bu bölümde, fonksiyon işaretçileri ve dönen kapanışlar da dahil olmak üzere fonksiyonlar ve kapanışlarla ilgili bazı gelişmiş özellikler incelenmektedir.

Fonksiyon İşaretçileri

Fonksiyonlara kapanışların nasıl aktarılacağından bahsetmiştik; normal fonksiyonları da fonksiyonlara aktarabilirsiniz! Bu teknik, yeni bir kapanış tanımlamak yerine daha önce tanımladığınız bir fonksiyonu geçirmek istediğinizde kullanışlıdır. Fonksiyonlar, Fn kapanış tanımı ile karıştırılmaması gereken fn (küçük f harfi ile) türüne zorlanır. fn türüne fonksiyon işaretçisi denir. Fonksiyonları fonksiyon işaretçileriyle geçirmek, fonksiyonları diğer fonksiyonlara argüman olarak kullanmanızı sağlar.

Bir parametrenin bir fonksiyon işaretçisi olduğunu belirtmek için kullanılan söz dizimi, parametresine bir ekleyen add_one fonksiyonunu tanımladığımız Liste 19-27'de gösterildiği gibi, kapanışlarınkine benzer. do_twice fonksiyonu iki parametre alır: bir i32 parametresi alan ve bir i32 döndüren herhangi bir fonksiyonun fonksiyon işaretçisi ve bir i32 değeri. do_twice fonksiyonu f işlevini iki kez çağırır, arg değerini geçirir ve ardından iki fonksiyon çağrısı sonucunu birbirine ekler. main fonksiyonu do_twice fonksiyonunu add_one ve 5 argümanlarıyla çağırır.

Dosya adı: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

Liste 19-27: Bir fonksiyon işaretçisini argüman olarak kabul etmek için fn türünü kullanma

Bu kod çıktısı The answer is: 12 olur. do_twice içindeki f parametresinin, i32 türünde bir parametre alan ve bir i32 döndüren bir fn olduğunu belirtiriz. Daha sonra f'i do_twice'ın gövdesinden çağırabiliriz. main içinde, add_one fonksiyon adını do_twice'a ilk argüman olarak aktarabiliriz.

Kapanışların aksine, fn bir özellikten ziyade bir türdür, bu nedenle Fn tanımlarından birine sahip yaygın tür parametresini bir tanım bağı olarak bildirmek yerine fn'yi doğrudan parametre türü olarak belirtiriz.

Fonksiyon işaretçileri, kapanış tanımlarının (Fn, FnMut ve FnOnce) üçünü de uygular, yani bir kapanış bekleyen bir fonksiyona argüman olarak her zaman bir fonksiyon işaretçisi aktarabilirsiniz. Fonksiyonlarınızı yaygın tür ve kapanış tanımlarından birini kullanarak yazmak en iyisidir, böylece fonksiyonlarınız hem fonksiyonları hem de kapanışları kabul edebilir.

Bununla birlikte, kapanışları değil de yalnızca fn'leri kabul etmek isteyeceğiniz bir örnek, kapanışları olmayan harici kodlarla arayüz oluştururken ortaya çıkar: C fonksiyonları argüman olarak fonksiyon kabul edebilir, ancak C'de kapanış yoktur.

Satır içi tanımlanmış bir kapanış ya da adlandırılmış bir fonksiyon kullanabileceğiniz bir örnek olarak, standart kütüphanedeki Iterator tanımı tarafından sağlanan map metodunun kullanımına bakalım. Sayılardan oluşan bir vektörü dizelerden oluşan bir vektöre dönüştürmek üzere map fonksiyonunu kullanmak için aşağıdaki gibi bir kapanış kullanabiliriz:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| i.to_string()).collect();
}

Veya kapanış yerine map argümanı olarak bir fonksiyonu şöyle adlandırabiliriz:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();
}

Daha önce “Gelişmiş Özellikler” bölümünde bahsettiğimiz tam nitelikli söz dizimini kullanmamız gerektiğini unutmayın çünkü to_string adında birden fazla fonksiyon mevcuttur. Burada, standart kütüphanenin Display'i sürekleyen tüm türler için süreklediği ToString tanımında tanımlanan to_string fonksiyonunu kullanıyoruz.

Bölüm 6'daki enum değerleri” bölümünde tanımladığımız her enum varyantının adının aynı zamanda bir başlatıcı fonksiyon olduğunu hatırlayın. Bu başlatıcı fonksiyonları, kapanış tanımlarını sürekleyen fonksiyon işaretçileri olarak kullanabiliriz; yani başlatıcı fonksiyonları, kapanışları alan metodlar için argüman olarak belirtebiliriz:

fn main() {
    enum Status {
        Value(u32),
        Stop,
    }

    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}

Burada, Status::Value'nun başlatıcı fonksiyonunu kullanarak map'in çağrıldığı aralıktaki her u32 değerini kullanarak Status::Value örnekleri oluşturuyoruz. Bazı insanlar bu stili tercih ederken bazıları da kapanışları kullanmayı tercih eder. Her ikisi de aynı koda derlenir, bu nedenle hangi stil sizin için daha iyiyse onu kullanın.

Dönen Kapanışlar

Kapanışlar tanımlar tarafından temsil edilir, bu da kapanışları doğrudan iade edemeyeceğiniz anlamına gelir. Bir tanım döndürmek isteyebileceğiniz çoğu durumda, bunun yerine fonksiyonun dönüş değeri olarak trait'i sürekleyen somut tipi kullanabilirsiniz. Ancak, kapanışlarda bunu yapamazsınız çünkü döndürülebilir somut bir tipleri yoktur; örneğin, fn fonksiyon işaretçisini bir dönüş tipi olarak kullanmanıza izin verilmez.

Aşağıdaki kod doğrudan bir kapanış döndürmeye çalışır, ancak derlenmez:

Here we create Status::Value instances using each u32 value in the range that map is called on by using the initializer function of Status::Value. Some people prefer this style, and some people prefer to use closures. They compile to the same code, so use whichever style is clearer to you.

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}

Derleyici hatası aşağıdaki gibidir:

$ cargo build
   Compiling functions-example v0.1.0 (file:///projects/functions-example)
error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0746`.
error: could not compile `functions-example` due to previous error

Hata yine Sized tanımına atıfta bulunuyor! Rust, kapanışı depolamak için ne kadar alana ihtiyaç duyacağını bilmiyor. Bu sorunun çözümünü daha önce görmüştük. Bir trait nesnesi kullanabiliriz:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

Bu kod sorunsuz derlenecektir. trait nesneleri hakkında daha fazla bilgi edinmek için Bölüm 17'deki “Farklı Türlerdeki Değerlere İzin Veren trait Nesnelerini Kullanma” başlığına bakabilirsiniz.

Şimdi devam edelim ve makrolara bakalım!