Rust'ın sahiplik modeli, belirli bir verinin ya bir adet değiştirilmiş referansı ya da herhangi sayıda değiştirilmeyen referansı olduğunu derleme zamanı kontrolü ile güvence altına almak için ödünç alma kontrolüne dayanır. Bu statik analiz, bellek koşulu (data races) ve kullanılmamış bellek hatalarını (use-after-free) çalışma zamanı maliyeti olmadan önler. Ancak, geri referanslarla grafik geçişleri veya paylaşılan duruma sahip yinelemeli veri yapıları gibi belirli algoritmik kalıplar, aliasing ilişkilerinin dinamik kontrol akışına bağlı olması nedeniyle derleyici tarafından güvenli olduğu kanıtlanamaz.
Temel zorluk, bir türün değişim sağlamak için değiştirilmemiş bir referans (&T) aracılığıyla mutasyon sunması gerektiğinde ortaya çıkar, bu da varsayılan özel değişim garantisini ihlal eder. Statik analiz, geri dönüşler veya döngüsel bağımlılıklar gibi karmaşık çalışma zamanı etkileşimleri boyunca referansların ömrünü takip edemez. Geriye dönüş mekanizması olmadan, bu geçerli ve güvenli kalıplar güvenli Rust içerisinde ifade edilemez, bu da geliştiricileri unsafe kod bloklarını kullanmaya zorlar.
RefCell, ödünç alma kontrolü mantığını derleme zamanından çalışma zamanına taşıyarak içsel değişkenlik (interior mutability) uygulamaktadır, bu işlem bir Cell<usize> aracılığıyla takip edilen bir durum makinesi kullanır. borrow() çağrıldığında, sayaç mevcuttaki iş parçacığına (thread) göre atomik olarak artar; borrow_mut() işlemine geçmeden önce sayacın sıfır olduğunu doğrular. Koruyucu türler (Ref<T> ve RefMut<T>) sayacı azaltmak için Drop'ı uygular, böylece ödünç alma sona erdiğinde durum sıfırlanır. Bu mekanizma ihlal durumunda panik oluşturarak tanımsız davranış üretmeden bellek güvenliğini dinamik olarak sürdürür.
use std::cell::RefCell; fn demonstrate_runtime_check() { let shared_vec = RefCell::new(vec![1, 2, 3]); // İlk değiştirilmiş ödünç alma let mut handle = shared_vec.borrow_mut(); handle.push(4); // Koruyucuyu bırakmak iç durumu sıfırlar drop(handle); // Sonraki değiştirilmemiş ödünç alma başarılı olur let read_handle = shared_vec.borrow(); assert_eq!(*read_handle, vec![1, 2, 3, 4]); }
Hiyerarşik bir belge düzenleyici oluştururken, mühendislik ekibi, çocuk Node nesnelerinin içerik değişikliklerini ebeveyn Container nesnelerine bildirebileceği bir Observer kalıbını uygulamak zorunda kaldı. Ebeveyn, çocukları iterasyon yaparak yerleşim hesaplama yaparken çocukların ebeveyne yeniden boyama tetiklemesi için değiştirilmiş erişim gerektiriyordu. Ödünç alma kontrolü, çocukların vektörünü iterasyona alırken ebeveyn üzerine değiştirilmiş bir referansı tutmayı engelledi.
Ekip, her düğümü Rc<RefCell<Node>> içine sararak çocuk düğümlerin ebeveynlerine Rc referanslarını kopyalayıp kullanmasına izin verdi. Olay yayılımı sırasında düğümler, ebeveyn durumunu değiştirmek için borrow_mut() çağrısında bulundu. Artılar: Bu yaklaşım geleneksel nesne yönelimli tasarımı yansıttı ve mimari değişiklik gerektirmedi. Eksiler: Ebeveyn, bir yerleşim hesaplaması yaparken (ödünç almışken) bir çocuk için değiştirilmiş ödünç alma bildiriminde bulunduğunda çalışma zamanında panik yaşandı. Bu hataları çözümlerinin anlaması için kapsamlı izleme gerektiriyordu.
Tüm düğümler, bir Arena yapısında Vec<Node> olarak saklandı ve ebeveyn-çocuk ilişkileri usize indeksleri ile temsil edildi. Yöntemler, herhangi bir düğümü indeksleme aracılığıyla değiştirme olanağı sağlamak için &mut Arena aldı. Artılar: Bu, çalışma zamanı ödünç alma kontrolü maliyetini ortadan kaldırdı ve eşleme ihlallerine karşı derleme zamanı garantileri sağladı. Eksiler: API çok kelimeli hale geldi, manuel indeks yönetimi gerektirdi ve düğüm silme işlemleri karmaşık mezar (tombstoning) veya kaydırma (shifting) mantığını gerektirdi ki bu da indekslerin geçersizleşme riskini artırdı.
Doğrudan değişim yerine, çocuk düğümler Command enum'ları (örneğin, RequestLayout(usize)) üretti ve bunları bir kuyrukta biriktirdi. Arena, iterasyon aşamasını tamamladıktan sonra bu kuyruğu işledi. Artılar: Bu, iç değişkenliği tamamen ortadan kaldırdı, güncellemeleri gruplayarak yapılmasını sağladı ve sistemin komut denetimi aracılığıyla test edilebilmesini sağladı. Eksiler: Olay üretimi ile işleme arasında gecikme yarattı ve komut üretimini yürütmeden ayırmak için kod tabanını yeniden düzenlemeyi gerektirdi.
Ekip, bir süre için Çözüm A ile prototipladı, ancak karmaşık kullanıcı etkileşimleri sırasında sık sık üretim paniği ile karşılaştı. Çözüm C'ye yeniden yapılandırdılar, bu da çalışma zamanı hatalarını ortadan kaldırarak endişelerin ayrışmasını iyileştirdi. Son sürüm, önbellek yerelini maksimize etmek için temel depolama katmanı olarak Çözüm B'yi kullanarak gösterdi ki, RefCell hızlı prototipleme sağlarken, derleme zamanı ödünç almayı saygı gösteren mimari kalıplar genellikle daha sağlam sistemler üretir.
Cevap: RefCell, OS senkronizasyon ilkelere duyarsız bir tek iş parçacıklı bağlamda çalışır. borrow_mut() etkin bir ödünç alma tespit ettiğinde, mevcut iş parçacığını beklemeye alamaz; çünkü bu, tek iş parçacıklı bir programı daimi olarak deadlock'a sokar. Bunun yerine, mantık hatasını işaretlemek için hemen panik yapar. Buna karşılık, Mutex atomik işlemler kullanır ve iş parçacıklarını beklemeye alabilir; bu, bir iş parçacığının kilidi serbest bırakana kadar beklemesine olanak tanır. Adaylar genellikle bunları birbirine karıştırır ve RefCell'ın panik yapısının, gerçek yanıtları gerektiren senaryolar ile ilişkili olduğunu gözden kaçırır; oysa Mutex, çelişkilerde panik yapmadan gerçek eşzamanlı süreçleri işleyebilir.
Cevap: Bir RefMut koruyucusu sızdırıldığında, RefCell'ın içsel değişken ödünç alma bayrağı kalıcı olarak açık kalır, böylece gelecekteki ödünç alımlara karşı hücreyi etkili bir şekilde dondurur. Ancak, bu bellek güvenliğini ihlal etmez çünkü bayrak yine de eşleme ilkesini (aliasing invariant) uygular - yeni değiştirilmiş ya da değiştirilmemiş ödünç alma işlemleri yapılamaz, bu da veri yarışlarını veya kullanılmamış bellek hatalarını engeller. Güvenlik garantisi, durum makinesinin yalnızca daha kısıtlayıcı durumlara geçişe izin vermesi gerektiğinden belirtilmektedir; sızıntılar temizleme işlemlerini engeller ancak hücreyi ihlallere izin veren bir duruma geçiremez. Adaylar genellikle sızdırılan koruyucuların tanımsız davranışa neden olduğunu varsayıyorlar, bu kaynak sızıntısını bellek güvenliği ihlalleri ile karıştırıyorlar.
Cevap: RefCell, T Send olduğunda Send olabilir çünkü birimler arası sahiplik transferi, aliasing yaratmaz - ödünç alma durumu nesne ile birlikte taşınır. Ancak, RefCell asla Sync olamaz çünkü içsel ödünç alma sayacı iş parçacığına güvenli değildir; iki iş parçacığı aynı anda erişirse, sayaç güncellemeleri üzerinde yarış olur, bu da T Sync olsa bile geçerlidir. Bu ayrım, RefCell'ın static değişkenlerde veya iş parçacıkları arasında Arc ile paylaşılmasında dış senkronizasyon olmaksızın tutulamayacağı anlamına gelir. Adaylar genellikle bunun Sync'ın yalnızca içeriğe (T) bağlı olduğunu varsayıyor, oysa bunun iç mekanizmanın iç senkronizasyonudur.