Yüksek mertebeden işlevler, diğer işlevleri parametre olarak alan veya bir sonuç olarak döndüren işlevlerdir. Rust, başlangıcından itibaren tür güvenliği ve performansa vurgu yapmıştır, bu da bu tür işlevlerle çalışmayı yansıtır.
Konuya Giriş:
Fonksiyonel dillerde yüksek mertebeden işlevler standart kabul edilirken, birçok sistem dilinde bu durum genellikle performans kaybına yol açmıştır (örn. tahsisatlardan veya kodun «inline» edilmesinin imkânsızlığından). Rust'ta bu işlevsellik, sıkı bir tür sistemi, statik yönlendirme veya trait'ler (Fn, FnMut, FnOnce) aracılığıyla uygulanır ve çoğu durumda ek maliyetlerden kaçınmaya olanak tanır.
Sorun:
Ana sorun, bir işlev veya closure'ı gönderirken tür güvenliğini sağlamak, değişkenleri yakalama yeteneğini (lambda ifadelerinin kolaylığı) ve tahsisatlar veya sanal çağrılar olmadan performansı sürdürmektir.
Çözüm:
Rust'ta yüksek mertebeden işlevler, generic parametreler ve işlevler/closure'lar için trait sarmalayıcıları aracılığıyla uygulanır. Standart trait’ler Fn, FnMut ve FnOnce, iletilen işlevin gereksinimlerini net bir şekilde belgelendirmeye olanak tanır (değiştirip değiştiremeyeceği veya çevresini tüketip tüketmeyeceği). Generic kullanarak, çağrılar derleme açısından inline edilebilir. Ayrıca, önceden bilinemeyen türler için Box<dyn Fn... aracılığıyla dinamik yönlendirme yapılır.
Kod örneği:
fn apply_to_vec<F: Fn(i32) -> i32>(v: Vec<i32>, f: F) -> Vec<i32> { v.into_iter().map(f).collect() } let nums = vec![1, 2, 3]; let doubled = apply_to_vec(nums, |x| x * 2); // doubled == [2, 4, 6]
Anahtar özellikler:
Fn, FnMut ve FnOnce'ın farkları nelerdir?
Birçok kişi bunun sadece sentaktik farklılıklar olduğunu veya Fn ile FnMut'in her şeyi değiştirilebilir bir şekilde yapabileceğini düşünür. Aslında:
FnOnce yalnızca bir kez çağrılabilir (örneğin, bir lambda yakalanan değeri içeri alıyorsa).FnMut, yakalanan ortamın durumunu değiştirebilir, ancak birçok kez çağrılabilir.Fn çevreyi değiştirmez.Örnek:
let mut sum = 0; let mut add = |x| { sum += x; }; // add FnMut'i uygular, ancak Fn'i değil
Bir işlevi değer olarak boxing olmadan iletmek mümkün müdür?
Çoğu zaman, tüm işlev argümanlarının mutlaka boxed (Box<dyn Fn...>) olması gerektiği düşünülür. Aslında, boxing yalnızca dinamik yönlendirme için gereklidir, tip numarası çalışma zamanında bilinmediğinde. Generic parametreler aracılığıyla işlev tamamen statik olarak türlendirilmiş olabilir, tahsisat ve box olmaksızın.
Closure ne zaman Copy olmaktan çıkar?
Bazıları, basit bir closure'ın her zaman Copy veya Clone olduğunu düşünür, eğer içindeki değişken Copy ise. Gerçekte, closure'lar varsayılan olarak Copy değildir, yakalanan değişkenler Copy olsa bile. Trait'i açıkça uygulamak ya da basit işlevlerle idare etmek gerekir.
Projede tüm callback'ler için yalnızca Box<dyn Fn()> kullanıldı, inline ve tahsisatlar hakkında düşünülmedi. Sonuçta performans artışı elde edilemedi, sık sık yapılan tahsisatlar gecikmelere yol açtı.
Artıları:
Eksileri:
Olay işleyicileri, FnMut trait kısıtlamasıyla generic işlevler aracılığıyla ayarlandı, tamamen tahsisat olmadan geçiş sağlandı.
Artıları:
Eksileri: