İçeriğe geç
academia.sh

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 Rc ile 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::clone ve RefCell yazı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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat