gokayburuc

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:

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:

  1. Paylaşımlı erişim: color_print closure'ı colorı yalnızca okur; dolayısıyla Fn olarak yakalar ve değişkeni immutable referansla ödünç alır. Aynı anda birden çok immutable referans geçerli olduğundan, &color referansı ile closure'ın bir arada yaşaması sorunsuzdur.

  2. Ö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:

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:

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.

#rust