Gelişmiş Fonksiyonlar ve Kapanışlar
Bu bölümde fonksiyonlar ve kapanışlarla ilgili bazı daha ileri özelliklere bakacağız. Bunların arasında fonksiyon işaretçileri ve kapanış döndürme de var.
Fonksiyon İşaretçileri
Kapanışları fonksiyonlara nasıl geçireceğimizi görmüştük; ama normal fonksiyonları da başka fonksiyonlara parametre olarak verebilirsiniz. Bu teknik, yeni bir kapanış tanımlamak yerine önceden tanımladığınız bir fonksiyonu geçirmek istediğinizde işe yarar. Fonksiyonlar, Fn kapanış trait’iyle karıştırılmaması gereken fn türüne zorlanır. fn türüne fonksiyon işaretçisi denir. Fonksiyon işaretçileri sayesinde fonksiyonları başka fonksiyonlara argüman olarak geçirebiliriz.
Bir parametrenin fonksiyon işaretçisi olduğunu belirtmenin sözdizimi, kapanışlara benzerdir. Liste 20-28’de add_one adlı, parametresine 1 ekleyen bir fonksiyon tanımlıyoruz. do_twice iki parametre alır: i32 alıp i32 döndüren herhangi bir fonksiyona işaret eden fonksiyon işaretçisi ve bir i32 değeri. do_twice, aldığı f fonksiyonunu arg değeriyle iki kez çağırır ve iki sonucu toplar. main ise do_twice’ı add_one ve 5 ile çağırır.
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!("Cevap: {answer}");
}
fn türünü kullanmakBu kod Cevap: 12 yazar. do_twice içindeki f parametresinin, bir i32 alıp i32 döndüren fn olduğunu belirtiyoruz. Sonra do_twice gövdesinde f’yi çağırabiliyoruz. main içinde de add_one fonksiyon adını ilk argüman olarak geçiyoruz.
Kapanışlardan farklı olarak fn bir trait değil, bir türdür. Bu yüzden parametre türü olarak doğrudan fn yazarız; trait sınırıyla jenerik parametre tanımlamayız.
Fonksiyon işaretçileri kapanış trait’lerinin üçünü de (Fn, FnMut, FnOnce) uygular. Yani kapanış bekleyen bir fonksiyona her zaman fonksiyon işaretçisi de geçebilirsiniz. Bu nedenle genellikle fonksiyonları, kapanış trait’lerinden biriyle sınırlandırılmış jenerik tür alacak şekilde yazmak daha esnektir; böylece hem fonksiyon hem kapanış kabul ederler.
Bununla birlikte, yalnızca fn kabul etmek isteyeceğiniz bir durum da vardır: kapanış kavramı olmayan dış kodla etkileşmek. Örneğin C fonksiyonları, fonksiyonları argüman olarak alabilir ama kapanışları alamaz.
Hem satır içinde tanımlanmış kapanış hem de isimli fonksiyon kullanılabilen bir örnek olarak standart kütüphanedeki Iterator trait’inin map metoduna bakalım. Sayılardan oluşan vektörü string vektörüne dönüştürmek için Liste 20-29’daki gibi 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();
}
map metodu ile kapanış kullanmakAynı işi, kapanış yerine isimli bir fonksiyon vererek de yapabiliriz. Liste 20-30 bunu gösteriyor.
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();
}
map ile String::to_string fonksiyonunu kullanmakBurada, aynı adlı birden çok fonksiyon bulunduğu için, 20-02 bölümünde anlattığımız tam nitelikli sözdizimine ihtiyaç duyarız.
Burada kullandığımız to_string, standart kütüphanenin Display uygulayan her tür için sağladığı ToString trait’indeki fonksiyondur.
Ayrıca 6. bölümdeki “Enum Değerleri” kısmından hatırlayın: tanımladığımız her enum varyantının adı, aynı zamanda başlatıcı fonksiyon olur. Bu başlatıcıları, kapanış trait’lerini uygulayan fonksiyon işaretçileri gibi kullanabiliriz. Liste 20-31, bunu map ile gösteriyor.
fn main() {
enum Durum {
Deger(u32),
Dur,
}
let list_of_statuses: Vec<Durum> = (0u32..20).map(Durum::Deger).collect();
}
Durum örnekleri üretmek için map içinde enum başlatıcısı kullanmakBurada, map çağrılan aralıktaki her u32 değeri için Durum::Deger başlatıcısını kullanarak Durum::Deger örnekleri oluşturuyoruz. Bazı geliştiriciler bu tarzı tercih eder, bazıları kapanış kullanmayı daha açık bulur. İkisi de aynı koda derlenir; sizin için hangisi daha anlaşılırsa onu kullanın.
Kapanış Döndürmek
Kapanışlar trait’lerle temsil edilir; bu yüzden doğrudan kapanış döndüremezsiniz. Trait döndürmek istediğiniz çoğu yerde, onun trait’i uygulayan somut türünü dönüş türü yapabilirsiniz. Ancak kapanışlarda bu çoğu zaman mümkün değildir; çünkü genellikle doğrudan yazılabilir somut bir dönüş türleri yoktur. Ayrıca, kapanış kapsamından değer yakalıyorsa onu fn dönüş türü olarak da kullanamazsınız.
Bunun yerine, genellikle 10. bölümde öğrendiğimiz impl Trait sözdizimini kullanırsınız. Fn, FnOnce ve FnMut kullanarak işlevsel bir tür döndürebilirsiniz. Örneğin Liste 20-32’deki kod sorunsuz derlenir.
#![allow(unused)]
fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
}
impl Trait sözdizimiyle kapanış döndürmekAma 13. bölümdeki “Kapanış Türlerini Çıkarsamak ve Açıklamak” kısmında belirttiğimiz gibi, her kapanış kendi başına ayrı bir türdür. Aynı imzaya sahip ama farklı uygulamalara sahip birden fazla işlevle çalışmanız gerekiyorsa, bunlar için trait nesnesi kullanmanız gerekir. Liste 20-33’te böyle bir durumda ne olduğuna bakalım.
fn main() {
let handlers = vec![returns_closure(), returns_initialized_closure(123)];
for handler in handlers {
let output = handler(5);
println!("{output}");
}
}
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 {
move |x| x + init
}
impl Fn türleri döndüren fonksiyonlarla tanımlanmış kapanışlardan Vec<T> oluşturmaya çalışmakBurada returns_closure ve returns_initialized_closure adlı iki fonksiyon var; ikisi de impl Fn(i32) -> i32 döndürüyor. Fakat döndürdükleri kapanışlar farklı. Bu kodu derlemeye çalışırsak Rust bunun çalışmayacağını söyler:
error[E0308]: mismatched types
--> src/main.rs:2:43
|
2 | let handlers = vec![returns_closure(), returns_initialized_closure(123)];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
...
8 | fn returns_closure() -> impl Fn(i32) -> i32 {
| ------------------- the expected opaque type
...
12 | fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 {
| ------------------- the found opaque type
|
= note: expected opaque type `impl Fn(i32) -> i32`
found opaque type `impl Fn(i32) -> i32`
= note: distinct uses of `impl Trait` result in different opaque types
Hata mesajı bize şunu söyler: impl Trait döndürdüğümüzde Rust benzersiz bir opak tür oluşturur. Bu, iç ayrıntılarını göremediğimiz ve kendi başımıza yazamayacağımız bir türdür. Dolayısıyla iki fonksiyon da aynı trait’i (Fn(i32) -> i32) uygulayan kapanış döndürse bile, Rust’ın bu iki dönüş için ürettiği opak türler birbirinden farklıdır. Bu, 17. bölümde gördüğümüz; aynı çıktı türüne sahip olsalar bile ayrı async bloklarının ayrı somut türlere sahip olmasına benzer. Bu sorunun çözümünü daha önce birkaç kez gördük: trait nesnesi kullanmak. Liste 20-34 bunu gösteriyor.
fn main() {
let handlers = vec![returns_closure(), returns_initialized_closure(123)];
for handler in handlers {
let output = handler(5);
println!("{output}");
}
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x + init)
}
Box<dyn Fn> döndüren fonksiyonlarla kapanışlardan Vec<T> oluşturmakBu sürüm sorunsuz derlenir. Trait nesneleri hakkında daha fazlası için 18. bölümdeki “Trait Nesneleriyle Ortak Davranışı Soyutlamak” kısmına bakabilirsiniz.
Sırada makrolar var!