Rust - Closures
Rust'ta Closure'ların Ortam Yakalaması: Ödünç Alma, Taşıma ve Sahiplik
Giriş
Closure (kapanış), tanımlandığı kapsamdaki değişkenlere erişebilen anonim bir fonksiyondur. Bu erişime yakalama (capture) denir ve Rust'ın sahiplik sistemi, bir closure'ın ortamını nasıl yakalayacağını katı kurallarla denetler. Rust'ta closure'lar kullanım biçimlerine göre üç yakalama stratejisinden birini benimser: immutable ödünç alma, mutable ödünç alma ve sahipliği taşıma. Bu makale, bu üç stratejiyi ve move anahtar kelimesinin etkisini örnek kodlar üzerinden incelemektedir.
Closure'lar ve Yakalama Modları
Bir closure, kapsamındaki her değişkeni en kısıtlayıcı olmayan yeterli biçimde yakalamaya çalışır:
- Değeri yalnızca okuyorsa: immutable ödünç alma (
Fn) - Değeri değiştiriyorsa: mutable ödünç alma (
FnMut) - Değeri tüketiyorsa veya sahiplenmesi gerekiyorsa: taşıma (
FnOnce)
Bu seçim derleyici tarafından otomatik yapılır; ancak sonuç, ödünç alma kurallarının tümünü closure'ın yakaladığı değişkenlere uygulanacağı anlamına gelir.
Immutable Yakalama ve Ödünç Alma Kuralları
let color = String::from("green");
// Closure, `color`ı immutable olarak ödünç alır.
let color_print = || println!("color: {}", color);
color_print(); // "color: green"
// `color`a yeni bir immutable referans almak sorunsuzdur.
let _reborrow = &color;
color_print(); // "color: green"
// `color` artık taşınabilir; çünkü `color_print` son kullanımı geride kaldı.
let _color_moved = color;
Bu bölüm iki önemli davranışı gösterir:
Paylaşımlı erişim:
color_printclosure'ıcolorı yalnızca okur; dolayısıylaFnolarak yakalar ve değişkeni immutable referansla ödünç alır. Aynı anda birden çok immutable referans geçerli olduğundan,&colorreferansı ile closure'ın bir arada yaşaması sorunsuzdur.Ömür bazlı serbestlik:
let _color_moved = color;satırıcolorın sahipliğini taşır. Normalde bu, hâlâcolorı ödünç alan bir closure varken hataydı. Ancak Rust'ın özgeçimli ömür (non-lexical lifetimes) analizi,color_print'in son çağrısından sonra öduncun sona erdiğini görür ve taşmaya izin verir. Ödünç alma kuralları koddaki yazım sırasına değil, gerçek kullanım sırasına göre denetlenir.
Mutable Yakalama ve Tek Yazıcı Kuralı
let mut count = 0;
// `count`ı yakalayan ve değiştiren closure: mutable ödünç.
let mut increment_counter = || {
count += 1;
println!("count: {}", count);
};
increment_counter(); // "count: 1"
// UYARI: Bu satır hata verir!
// let _reborrow = &count;
// increment_counter();
// Bu ise çalışır; çünkü closure artık çağrılmayacak.
let _count_reborrowed = &mut count;
Closure'ın countı değiştirmesi, onu mutable olarak yakalamasını zorunlu kılar (FnMut; çağrının da mut ile yapılması gerekir). Ödünç alma kuralları burada aynen geçerlidir:
- Hatalı senaryo:
&countreferansı varkenincrement_counter()çağrısı, aynı değere aynı anda hem paylaşılan hem mutable erişim uygular. Rust, tek yazıcı kuralını ihlal ettiği için bu kodu reddeder. Hata mesajı açıklayıcıdır:cannot borrow count as mutable because it is also borrowed as immutable. - Çalışan senaryo:
&mut countise closure'ın son çağrısından sonra alınır; bu nedenle kural ihlali oluşmaz.
Bu örnek, closure yakalamasının sıradan bir referans almakla özdeş olduğunu gösterir: closure'lar ödünç alma kurallarının dışında değildir, onları otomatik olarak devralır.
Sahipliğin Taşınması: Box<T> Örneği
use std::mem;
let movable = Box::new(3);
// Closure, `movable`ı yakalar ve tüketir.
let consume = || {
println!("movable: {:?}", movable); // "movable: 3"
mem::drop(movable); // sahiplik kapanış içinde bırakılır.
};
consume();
// `movable` artık kullanılamaz.
Closure movableı mem::drop ile tükettiğinden, tek geçerli yakalama biçimi sahipliği taşımaktır; closure FnOnce olur ve yalnızca bir kez çağrılabilir. Closure çağrıldığı anda movableın sahipliği düşer ve kapsamdaki sonraki herhangi bir kullanım derleme hatası üretir: value used after move.
move Anahtar Kelimesi
let haystack = vec![1, 2, 3];
// `move`, sahipliği closure'a zorlar.
let contains = move |needle| haystack.contains(needle);
println!("{}", contains(&1)); // true
println!("{}", contains(&4)); // false
// println!("There're {} elements in vec", haystack.len()); // HATA
move anahtar kelimesi, derleyicinin otomatik seçimini devre dışı bırakır ve yakalanan her değişkenin sahipliğini closure'a zorla taşır:
haystackbirVecolduğu içinCopysemantiği yoktur; taşınan veridir.movesonrasıhaystackana kapsamda kullanılamaz.containsise durumu kendi sahipliği altında tuttuğundan, tanımlandığı kapsamdan uzun ömürlü olabilir. Bu, closure'ı bir fonksiyondan döndürmek veya başka bir iş parçacığına (thread::spawn) göndermek için zorunludur: taşınmamış bir ödünç, kapsamın sonunda geçersiz hale geleceğinden derleyici buna izin vermez.
move'un kaldırılması durumunda ise closure haystackı yalnızca immutable olarak ödünç alır (Fn) ve haystack ana kapsamda kullanılabilir kalır. Bu, ödünç almanın yeterli olduğu durumlarda daha esnek bir seçenektir.
Yakalama Stratejilerinin Karşılaştırması
| Kullanım | Yakalama | Trait | Ödünç kuralı etkisi |
|---|---|---|---|
| Değeri yalnızca okuma | Immutable ödünç | Fn |
Paylaşımlı erişim serbesttir |
| Değeri değiştirme | Mutable ödünç | FnMut |
Aynı anda başka referans yasaktır |
| Değeri tüketme | Taşıma | FnOnce |
Tek çağrı; değer closure'a aittir |
move ile zorlama |
Taşıma | Fn/FnOnce |
Değer ana kapsamda geçersizdir |
Sonuç
Rust'ta closure'lar, ödünç alma kurallarının aynısını kullanarak ortamlarını yakalar: okuma paylaşımlı ödünç, değiştirme mutable ödünç, tüketme taşıma ile sonuçlanır. Derleyicinin bu seçimi otomatik yapması kodu sadeleştirir; move anahtar kelimesi ise kapsamı aşan closure'larda (döndürme, iş parçacığına gönderme) gerekli olan sahiplik aktarımını sağlar. Örneklerin ortak dersi şudur: bir closure'ın yakaladığı değişken, kuralları tam olarak normal bir referans gibi izler; fark, referansın görünürlüğünün closure'ın yaşam döngüsüne bağlı olmasıdır.