Ders 14 / 14
Döngüsel Referanslar
İki değer RefCell üzerinden birbirini Rc ile tuttuğunda güçlü sayacın hiçbir zaman sıfıra inmediği ve defterde hiçbir kaydın oluşmadığı ölçülüyor — kursun tek sızıntısı. Bir zayıf referansın sayacı artırmadan aynı döngüsel referansı kırdığı ve kırıldıktan sonra defterde iki kaydın birden oluştuğu koşturuluyor; güçlü ve zayıf sayaç değerleri basılıyor.
Yayın Güncelleme
Bu derste
- İki değerin Rc üzerinden birbirini tuttuğunda güçlü sayacın hiç sıfıra inmediğini ve defterde kayıt oluşmadığını koşturarak gösterir
- Bir zayıf referansın güçlü sayacı artırmadan döngüsel referansı kırdığını ve kırıldıktan sonra defterde iki kaydın oluştuğunu kanıtlar
- Güçlü ve zayıf referans sayaçlarının aynı değer üzerinde bağımsız izlendiğini örnekleyerek ayırt eder
İçindekiler
Kursun üç konusu ve on üç dersi boyunca bir değerin ömrü hep dört sınıftan birine düştü ve
sıfırıncı bir seçenek hiç görülmedi — Rc ile Arcın sayacı bile, kaç kez kopyalanırsa
kopyalansın, er ya da geç sıfıra indi. Bu son ders tam bu varsayımı bozan tek durumu ölçüyor:
iki değer, RefCell üzerinden birbirinin içine birer Rc koyarsa, güçlü sayaç hiçbir zaman
sıfıra inmiyor ve defterde hiçbir kayıt oluşmuyor. Kursun on üç dersi boyunca ölçülen her
bitiş kaynaktan okunabilir, belirlenimci bir noktaya sahipti; bu ders o belirlenimciliğin
kendi sınırını — bir bitişin hiç gerçekleşmeyebileceğini — gösteriyor.
Eşzamanlılık kursunda (M09/K03) ölçülen sızıntı, durması beklenen ama hiç durmayan bir
goroutine’di: koşan bir birim sızıyordu. Burada sızan bir birim değil bir değer — kaynak
kodun kendisi tamamen sıradan biçimde biter, program sonlanır, ama iki Dugum değeri bu
sondan sonra da bellekte kalır, hiçbir zaman bırakılmaz.
İki Değer Birbirini Tutunca
Çekirdeğin Defter, Iz ve Drop uygulaması burada da aynı kesitle kullanılıyor. Üstüne
tek bir yeni tip ekleniyor: Dugum, kendi Izsini ve bir sonraki düğüme güçlü bir referans
tutan sonraki: RefCell<Option<Rc<Dugum>>> alanını taşıyor. İki Dugum değeri (a, b)
Rc::new ile kuruluyor, sonra her biri ötekine Rc::clone ile bir referans veriyor —
Referans Sayımı dersinde ölçülen kopyalama işleminin ta kendisi, burada
yalnızca karşılıklı yönde uygulanıyor.
PS13. — “iki değerin birbirini tuttuğunda güçlü sayacın kapsam kapandıktan sonra bile
sıfıra inmediği” iddiası, aşağıdaki koşumun kapsam kapaninca defter uzunlugu: 0
satırıyla doğrulanıyor.
// dongu_olusumu.rs — iki degerin birbirini tutmasi: sayac hic sifira inmiyor, defter bos kaliyor
use std::cell::RefCell;
use std::rc::Rc;
type Defter = Rc<RefCell<Vec<String>>>;
struct Iz {
ad: String,
defter: Defter,
}
impl Iz {
fn yeni(ad: &str, defter: &Defter) -> Iz {
Iz { ad: ad.to_string(), defter: Rc::clone(defter) }
}
}
impl Drop for Iz {
fn drop(&mut self) {
self.defter.borrow_mut().push(self.ad.clone());
}
}
struct Dugum {
iz: Iz,
sonraki: RefCell<Option<Rc<Dugum>>>,
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
{
let a = Rc::new(Dugum {
iz: Iz::yeni("dugum-a", &defter),
sonraki: RefCell::new(None),
});
let b = Rc::new(Dugum {
iz: Iz::yeni("dugum-b", &defter),
sonraki: RefCell::new(None),
});
*a.sonraki.borrow_mut() = Some(Rc::clone(&b));
*b.sonraki.borrow_mut() = Some(Rc::clone(&a));
println!("a adi: {}, sayac: {}", a.iz.ad, Rc::strong_count(&a));
println!("b adi: {}, sayac: {}", b.iz.ad, Rc::strong_count(&b));
}
println!("kapsam kapaninca defter uzunlugu: {}", defter.borrow().len());
}
a adi: dugum-a, sayac: 2 b adi: dugum-b, sayac: 2 kapsam kapaninca defter uzunlugu: 0
Kap dolu iken sayaç ikisinde de iki: anın kendi bağlaması bir, b.sonrakinin tuttuğu
kopya bir; b için de simetrik. Kapsam kapanınca a ve b bağlamaları bildirimin tersi
sırayla bitiyor — Yığıt ve Öbek dersinde kurulan kural burada da geçerli. Ama her
bağlamanın bitmesi yalnızca kendi sayacını bir azaltıyor: b bittiğinde a.sonrakinin
tuttuğu kopya hâlâ bnin Dugumını canlı tutuyor; a bittiğinde de b.sonrakinin
tuttuğu kopya anınkini. Geriye kalan sayaç ikisinde de bir — hiçbir zaman sıfır olmuyor.
Dugum::iz alanının Dropu bu yüzden hiç tetiklenmiyor, çünkü Dugumın kendisi hiç
bırakılmıyor; defter, koşumun sonuna kadar boş kalıyor.
Zayıf Referans Sayacı Artırmıyor, Döngüsel Referansı Kırıyor
Bu döngüsel referansı kırmanın yolu, iki yönden birini zayıf bir referansa çevirmek:
Weak<T>, Rc<T> gibi aynı değere işaret eder ama güçlü sayacı hiç artırmaz. Dugum
burada ikiye ayrılıyor — guclu_sonraki bir yönü güçlü tutuyor, zayif_onceki öbür yönü
zayıf tutuyor. Hangi yönün zayıf olacağı bir tasarım kararı; dilin kendiliğinden verdiği
bir yanıt değil.
PS14. — “bir zayıf referansın güçlü sayacı hiç artırmadığı” iddiası, aşağıdaki koşumun
a adi: kirilan-a, guclu: 1, zayif: 1 satırıyla doğrulanıyor: b, aya zayıf referans
tutuyor ve anın güçlü sayacı hâlâ bir.
// zayif_ile_kirma.rs — zayif referans sayaci artirmiyor ve dongusel referansi kiriyor
use std::cell::RefCell;
use std::rc::{Rc, Weak};
type Defter = Rc<RefCell<Vec<String>>>;
struct Iz {
ad: String,
defter: Defter,
}
impl Iz {
fn yeni(ad: &str, defter: &Defter) -> Iz {
Iz { ad: ad.to_string(), defter: Rc::clone(defter) }
}
}
impl Drop for Iz {
fn drop(&mut self) {
self.defter.borrow_mut().push(self.ad.clone());
}
}
struct Dugum {
iz: Iz,
guclu_sonraki: RefCell<Option<Rc<Dugum>>>,
zayif_onceki: RefCell<Weak<Dugum>>,
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
{
let a = Rc::new(Dugum {
iz: Iz::yeni("kirilan-a", &defter),
guclu_sonraki: RefCell::new(None),
zayif_onceki: RefCell::new(Weak::new()),
});
let b = Rc::new(Dugum {
iz: Iz::yeni("kirilan-b", &defter),
guclu_sonraki: RefCell::new(None),
zayif_onceki: RefCell::new(Weak::new()),
});
*a.guclu_sonraki.borrow_mut() = Some(Rc::clone(&b));
*b.zayif_onceki.borrow_mut() = Rc::downgrade(&a);
println!("a adi: {}, guclu: {}, zayif: {}", a.iz.ad, Rc::strong_count(&a), Rc::weak_count(&a));
println!("b adi: {}, guclu: {}, zayif: {}", b.iz.ad, Rc::strong_count(&b), Rc::weak_count(&b));
let geri_donen = b.zayif_onceki.borrow().upgrade();
println!("b uzerinden a'ya geri donus basarili mi: {}", geri_donen.is_some());
}
println!("kapsam kapaninca defter: [{}]", defter.borrow().join(", "));
}
a adi: kirilan-a, guclu: 1, zayif: 1 b adi: kirilan-b, guclu: 2, zayif: 0 b uzerinden a'ya geri donus basarili mi: true kapsam kapaninca defter: [kirilan-a, kirilan-b]
anın güçlü sayacı bir kalıyor, çünkü yalnızca kendi bağlaması ona sahip; bnin zayıf
sayacı bir, çünkü b.zayif_onceki ona bakıyor ama sahiplenmiyor. bnin güçlü sayacı ise
iki — kendi bağlaması ve a.guclu_sonrakinin tuttuğu kopya. upgrade() çağrısı, zayıf
referansın hâlâ geçerli bir değere baktığını doğruluyor: bir Option<Rc<Dugum>>
döndürüyor ve a hâlâ yaşadığı için sonuç Some.
PS15. — “bu döngüsel referans kırıldıktan sonra defterde iki kaydın birden oluştuğu”
iddiası, aynı koşumun son satırındaki defter: [kirilan-a, kirilan-b] ile doğrulanıyor.
Kapsam kapanınca b önce bitiyor ve güçlü sayacını ikiden bire indiriyor — a.guclu_sonraki
hâlâ bir kopya tuttuğu için bnin Dugumı henüz bırakılmıyor. a bitince kendi sayacı
sıfıra iniyor ve gerçekten bırakılıyor; bu bırakma önce kendi iz alanını (kirilan-a)
deftere yazıyor, sonra kendi guclu_sonraki alanını bırakırken bye olan son güçlü
referansı da düşürüyor — bnin sayacı da sıfıra iniyor ve b de bırakılıyor
(kirilan-b). Zayıf referans bu zincire hiç girmiyor: b.zayif_onceki bırakılırken hiçbir
güçlü sayaç değişmiyor, çünkü zaten hiç saymamıştı.
Sınırlayıcı Ölçüm: Derleyici Döngüsel Referansı Görmüyor
Yukarıdaki iki koşum arasındaki fark, kaynakta hiçbir redd olmadan geçiyor. dongu_olusumu.rs
da, zayif_ile_kirma.rs de aynı satırlarla derleniyor: Rc::new, Rc::clone, bir
RefCell alanına yazma — Referans Sayımı dersinden beri (PS5) kullanılan işlemlerin ta
kendisi. Ödünç Denetleyicisi dersi, denetleyicinin sahiplik disiplinini sınadığını ama
kodun doğruluğunu güvence altına almadığını göstermişti; burada aynı sınır bir adım daha
genişliyor: derleyici, iki Rc değerinin birbirini tuttuğunu hiçbir zaman fark etmiyor,
çünkü her Rc::clone çağrısı kendi başına geçerli bir işlem — sayaç artırma, hangi değere
işaret ettiğine bakmaksızın izin veriliyor.
PS16. — “birbirini tutan bir kaynağın, birbirini tutmayan bir kaynakla aynı derleme
yolundan geçtiği” iddiası, iki koşumun da (dongu_olusumu, zayif_ile_kirma) hiçbir uyarı
ya da hata vermeden derlenmesiyle doğrulanıyor.
Bu, kursun bütün derslerinde ölçülen belirlenimciliğin bir istisnası değil, kendi sınırı:
bitiş noktası hâlâ kaynaktan okunabiliyor, yalnız bu kez okunan şey bir bitişin hiç yer
almayacağı. Döngüsel referansı kırmak bu yüzden derleyicinin değil yazanın sorumluluğu;
hangi yönün zayıf olacağını seçmek de öyle — bnin ayı zayıf tutması yerine anın
byi zayıf tutması seçilseydi aynı kırılma ters yönde gerçekleşirdi, sonuç değişmezdi.
Kapanış Tablosu
On dört ders boyunca ölçülen on dört bitiş noktası aşağıda toplanıyor. Her satırın sınıfı ilgili dersin kendi metninde yazılıdır; tablo yeni bir sınıflama yapmıyor, dağınık olanı bir araya getiriyor.
| Ders | Ölçülen değer | Ömrün bittiği yer | Sınırlayıcı ölçüm |
|---|---|---|---|
| Yığıt ve Öbek | kapsam-1/2/3 bitiş sırası |
kapsam sonu | bitiş sırası bildirimin tam tersi |
| Sahiplik ve Taşıma | islev-icine, ic-kapsama, yapi-icine |
taşındığı yerde | taşıma kopyalama değil, defterde tek kayıt |
| Ödünç Alma | odunc-1 (paylaşımlı + değiştirilebilir ödünç) |
kapsam sonu | ödünç ömrü uzatmaz, referans kaynağı aşamaz |
| Dilimler | goruntu (liste dilimi) |
kapsam sonu | dilim yalnız aralığı değil kaynağın tamamını kilitler |
| Ödünç Denetleyicisi | ekle_ve_say dönüş değeri |
kapsam sonu | denetleyici doğruluğu değil erişim disiplinini sınar |
| Yaşam Süresi Kavramı | r1, r2, r3 referans aralığı |
kapsam sonu | iki bağımsız aralık iç içe olmak zorunda değil |
| Açık Yaşam Süresi Açıklamaları | oncelikli_ad, digerinin_adi |
kapsam sonu | açıklama bir istektir, sözü tutmayan gövde reddedilir |
| Yaşam Süresi Çıkarım Kuralları | ilk_uc_harf, daha_uzun_olan |
kapsam sonu | çıkarım kolaylıktır, kapsamadığı imzada açıklama zorunlu |
| Yapılarda Referans Tutma | SahipYapi, OduncYapi |
taşındığı yerde | yapı, yaşam süresi parametresi olmadan referans taşıyamaz |
| Öbekte Tekil Sahiplik | obekteki-tekil, liste-1..3 |
kabıyla birlikte | aynı tip iki farklı sınıfa düşebilir, sınıf işlemden gelir |
| Referans Sayımı | paylasilan (Rc) sayacı: 1, 2, 3, 2 |
kabıyla birlikte | bitişi belirleyen kopyanın kimliği değil kalan sayısı |
| Atomik Referans Sayımı | paylasilan (Arc) sayacı: 1, 2, 3, 2 |
kabıyla birlikte | atomik sayaç veriyi değil yalnız sayacı korur |
| İç Değişebilirlik | sayac (RefCell) ödünç denemeleri |
kabıyla birlikte | kural kaybolmuyor, çalışma zamanında Erre dönüşüyor |
| Döngüsel Referanslar | dugum-a, dugum-b (birbirini tutan) |
hiç bitmedi | derleyici görmüyor, aynı Rc::clone hem paylaşımı hem sızıntıyı kurar |
Özet
- İki değer
Rcile birbirini tuttuğunda güçlü sayaç hiçbir zaman sıfıra inmiyor; defterde kayıt oluşmuyor — kursun tek sızıntısı. - Zayıf referans (
Weak<T>) aynı değere işaret eder ama güçlü sayacı hiç artırmaz; bir yönü zayıf tutmak döngüsel referansı kırmaya yetiyor. - Döngüsel referans kırıldıktan sonra kapsam kapanınca her iki değer de bırakılır ve defterde iki kayıt birden oluşur.
- Derleyici bu döngüsel referansı hiçbir zaman reddetmiyor;
Rc::cloneveRefCellyazımı, karşılıklı olmayan kullanımla aynı derleme yolundan geçiyor. - M09/K03’te sızan şey koşan bir birimdi, burada sızan bir değer; ikisi de dışarıdan bir sinyal gelmeden kendiliğinden sona ermiyor.
Kurs Kapanışı
On dört ders boyunca bu kurs tek bir soruyu sordu: bir değerin ömrü nerede bitiyor? Yanıt her zaman kaynaktan okunabilir kaldı — kapsam sonunda, taşındığı yerde, açık bir çağrıda, bir kabıyla birlikte ya da, bu derste görüldüğü gibi, hiç. Kursun ilk konusu, Sahiplik Kuralları, bu sınıfları ve onları koruyan ödünç denetleyicisini kurdu; ikinci konusu, Yaşam Süreleri, bu denetimin adını (yaşam süresi) ve ne zaman yazılması gerektiğini verdi; üçüncü konusu tekliği aşıp sahipliği sayılabilir, paylaşılabilir ve en sonunda değiştirilebilir hâle getirdi — ama her adımda bitiş noktası hâlâ belirlenimciydi, ta ki bu derste iki değerin birbirini tuttuğu görülene kadar.
Kursun on üç dersi boyunca bir kavram yalnızca adıyla geçti ve tanımlanmadan bırakıldı: Drop
trait’i. Kapanış (closure) ve yineleyici (iterator) bu kursta hiç geçmedi; üçü de aynı soruyu
paylaşıyor — bir değerin ne yapabildiği, ne kadar yaşadığından ayrı bir soru — ve sıradaki
kurs tam bu soruyu açıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.