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); }
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!