İçeriğe geç
academia.sh

Ders 01 / 14

Yığıt ve Öbek

On dört değerlik bir ölçüm çekirdeği koşturulur ve her değerin ömrünü dört sınıftan birine yerleştirir — kapsam sonu, taşındığı yerde, açık çağrıda, kabıyla birlikte; sonuç 6/3/1/4 olarak sabittir. Kapsam sonunda biten değerlerin bitiş sırasının bildirim sırasının tam tersi olduğu ve öbeğe alınan bir değerin de aynı deftere düştüğü ölçülür.

Yayın Güncelleme

Bu derste

  • Dört sınıfı (kapsam sonu, taşındığı yerde, açık çağrıda, kabıyla birlikte) tanımlar ve birbirinden ayırır
  • Ölçüm çekirdeğini koşturarak on dört değerin 6/3/1/4 dağılımını doğrular
  • Kapsam sonunda biten değerlerin bitiş sırasının bildirim sırasının tersi olduğunu koşturarak gösterir
İçindekiler

Önceki kurs kaynakta yazılan bir satırın ne ürettiğini ölçtü: ifade, deyim, öğe. Bu üçlü sınıflandırma bir satırın çalıştırıldığı anda ne olduğunu belirliyordu, ama bilerek sormadığı bir soru kaldı — o satırın ürettiği değer ne kadar yaşıyor. Bu kurs tam oraya bakıyor ve bütün dersleri boyunca tek bir soru soruluyor: bu değerin ömrü nerede bitiyor?

Bu soru daha önce farklı bir bağlamda karşımıza çıkmıştı. Eşzamanlılık kursu (M09/K03) bir goroutine’in nasıl duracağını ölçtü — koşan bir birimin sonlanması. Bu kurs aynı fiili farklı bir özneye uyguluyor: orada koşan bir birim duruyordu, burada bir değer bitiyor. Bir birimin durması dışarıdan bir kararla (kanal kapanması, bağlam iptali) tetiklenirken, bir değerin bitişi kaynağın kendi yapısından okunabiliyor — ayrı bir karar mekanizmasına ihtiyaç duymadan.

Dört Yanıt

Rust’ta bir değerin ömrü dört biçimden birinde bitiyor ve kurs boyunca bu dört ad değişmiyor.

Sınıf Tanım
kapsam sonu Değer, bağlandığı bloğun sonunda biter
taşındığı yerde Sahiplik başkasına geçti; ömür yeni sahibin bittiği yerde biter
açık çağrıda Kaynakta yazılı bir çağrı ömrü erken bitirir
kabıyla birlikte Değer bir kabın içindedir; kap bitince o da biter

Bu dört sınıf birbirini dışlıyor: bir değer aynı anda hem kapsam sonunda hem taşındığı yerde bitemez, çünkü taşınan bir değerin eski bağlaması zaten canlı değil — bitiş, bir değerin hayatında yalnız bir kez, dört yoldan tam biriyle gerçekleşiyor. Bitiş noktası kaynağın kendi yapısından okunuyor ve bir çalışma zamanı kararına bağlı değil; bu yüzden ölçüm bir süre ya da bir sayaç değil, sıralı bir kayıt üretiyor — hangi değerin ne zaman bittiğinin listesi.

Programlama Temelleri kursu (M01/K02) kapsam (scope) kavramını zaten kurmuştu: bir adın hangi blokta göründüğü ve o bloktan çıkınca adın erişilemez hâle geldiği. Bu kurs aynı kavramı tekrar tanımlamıyor; pay, Rust’ın kapsamı yalnız bir görünürlük sınırı olarak değil, bir bitiş noktası olarak da kullanmasında. Bir adın kapsam dışına çıkması, çoğu dilde yalnız o ada bir daha erişilemeyeceği anlamına gelir; burada aynı olay, adın tuttuğu değerin de sonlandığı anlamına geliyor — ve bu sonlanma, derleme zamanında bilinen bir noktada, çalışma zamanında kesin olarak gerçekleşiyor.

Ölçüm Çekirdeği

Kursun geri kalanında kullanılan çekirdek omur.rs’dir. On dört ayrı değer dört bölüm halinde (A, B, C, D) oluşturuluyor, her değerin bitişi ortak bir gözlem defterine yazılıyor ve sonunda sınıf başına toplam basılıyor.

Her bölüm kendi ölçümünü aynı yöntemle alıyor: bölüm başlamadan önce defterin o anki uzunluğu (a0, b0, c0, d0) kaydediliyor, bölümün kapsamı kapandıktan sonra yeni_olanlar işlevi yalnız o uzunluktan sonra eklenen satırları döndürüyor. Bu, dört bölümün birbirinin ölçümüne karışmasını engelliyor — A bölümünde biten üç değer, B bölümünün ölçümünde bir daha görünmüyor, çünkü B’nin başlangıç noktası zaten A bittikten sonraki uzunluğu alıyor. Aynı defter dördü için de ortak, ama her bölüm kendi payını kendi başlangıç noktasından itibaren okuyor.

Gözlem defteri Defter tipiyle tutuluyor — birden çok Iz örneğinin aynı listeye yazabilmesini sağlayan paylaşılan bir kayıt. Bu paylaşımın kendi iç yapısı (Rc<RefCell<Vec<String>>>) ölçüm aracının bir tasarım kararıdır; nasıl çalıştığı Referans Sayımı ve İç Değişebilirlik derslerinde ayrı birer konu olarak ele alınacak. Burada önemli olan yalnız şu: her Iz değeri Drop trait’ini uyguluyor, yani bir değerin ömrü bittiğinde derleyici otomatik olarak bir kapanış kodu çalıştırıyor ve bu kod kendi adını deftere ekliyor. Drop’un kendi kuralları (hangi tiplerin onu uygulayabildiği, Clone/Copy ile ilişkisi) bu kursun konusu değil; ölçülen tek şey bu tetiklenmenin kaynakta nerede gerçekleştiği.

SK1. Defter ve Iz’in kendisi ölçüm aracının tasarım kararıdır, dilin bir zorunluluğu değil; bir değerin hangi sınıfa girdiğine karar veren şey kodun kendi yapısıdır — bir kapsamın kapanması, bir taşıma, açık bir çağrı ya da bir kabın içinde olmak. Iz tipi bu dört durumu görünür kılmak için yazıldı, kendisi bunlardan biri değil.

// omur.rs — bir degerin omru nerede bitiyor: kapsam sonunda mi, tasindigi yerde mi,
// acik cagrida mi, yoksa kabiyla birlikte mi.

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 Kutu {
    icerik: Iz,
}

fn yut(_alinan: Iz) {
    // Deger buraya tasindi; govde bitince omru burada bitiyor.
}

fn okunan(defter: &Defter) -> Vec<String> {
    defter.borrow().clone()
}

fn yeni_olanlar(defter: &Defter, onceki: usize) -> String {
    let hepsi = okunan(defter);
    hepsi[onceki..].join(", ")
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    let mut sayim: Vec<(&str, &str)> = Vec::new();

    // --- A: kapsam sonu ---
    let a0 = okunan(&defter).len();
    {
        let _birinci = Iz::yeni("kapsam-1", &defter);
        let _ikinci = Iz::yeni("kapsam-2", &defter);
        let _ucuncu = Iz::yeni("kapsam-3", &defter);
    }
    println!("A kapsam sonu       -> {}", yeni_olanlar(&defter, a0));
    for ad in ["kapsam-1", "kapsam-2", "kapsam-3"] {
        sayim.push((ad, "kapsam sonu"));
    }

    // Golgelenen deger de kapsam sonuna kadar yasar: yeni baglama eskisini bitirmiyor.
    let g0 = okunan(&defter).len();
    {
        let _golge = Iz::yeni("golgelenen", &defter);
        let _golge = Iz::yeni("golgeleyen", &defter);
        println!("   golgeleme aninda -> {}", yeni_olanlar(&defter, g0));
    }
    println!("A golgeleme         -> {}", yeni_olanlar(&defter, g0));
    sayim.push(("golgelenen", "kapsam sonu"));
    sayim.push(("golgeleyen", "kapsam sonu"));

    // --- B: tasindigi yerde ---
    let b0 = okunan(&defter).len();
    {
        let tasinan = Iz::yeni("islev-icine", &defter);
        yut(tasinan);
        println!("   islev doner donmez -> {}", yeni_olanlar(&defter, b0));

        let ic = Iz::yeni("ic-kapsama", &defter);
        {
            let _yeni_sahip = ic;
        }
        println!("   ic kapsam biter bitmez -> {}", yeni_olanlar(&defter, b0));

        let alan = Iz::yeni("yapi-icine", &defter);
        {
            let kutu = Kutu { icerik: alan };
            let _ad_uzunlugu = kutu.icerik.ad.len();
        }
    }
    println!("B tasindigi yerde   -> {}", yeni_olanlar(&defter, b0));
    for ad in ["islev-icine", "ic-kapsama", "yapi-icine"] {
        sayim.push((ad, "tasindigi yerde"));
    }

    // --- C: acik cagrida ---
    let c0 = okunan(&defter).len();
    {
        let erken = Iz::yeni("erken-birakilan", &defter);
        let _gec = Iz::yeni("kapsamda-kalan", &defter);
        drop(erken);
        println!("   acik cagridan sonra -> {}", yeni_olanlar(&defter, c0));
    }
    println!("C acik cagrida      -> {}", yeni_olanlar(&defter, c0));
    sayim.push(("erken-birakilan", "acik cagrida"));
    sayim.push(("kapsamda-kalan", "kapsam sonu"));

    // --- D: kabiyla birlikte ---
    let d0 = okunan(&defter).len();
    {
        let mut liste = Vec::new();
        liste.push(Iz::yeni("kap-1", &defter));
        liste.push(Iz::yeni("kap-2", &defter));
        liste.push(Iz::yeni("kap-3", &defter));
        let _obekte = Box::new(Iz::yeni("obekte", &defter));
        println!("   kap dolu iken -> [{}]", yeni_olanlar(&defter, d0));
    }
    println!("D kabiyla birlikte  -> {}", yeni_olanlar(&defter, d0));
    for ad in ["kap-1", "kap-2", "kap-3", "obekte"] {
        sayim.push((ad, "kabiyla birlikte"));
    }

    // --- rapor ---
    let biten = okunan(&defter);
    println!();
    println!("toplam biten deger: {}", biten.len());

    let siniflar = ["kapsam sonu", "tasindigi yerde", "acik cagrida", "kabiyla birlikte"];
    for sinif in siniflar {
        let adet = sayim.iter().filter(|(_, s)| *s == sinif).count();
        println!("{:<18}{}", sinif, adet);
    }
}
A kapsam sonu       -> kapsam-3, kapsam-2, kapsam-1
   golgeleme aninda -> 
A golgeleme         -> golgeleyen, golgelenen
   islev doner donmez -> islev-icine
   ic kapsam biter bitmez -> islev-icine, ic-kapsama
B tasindigi yerde   -> islev-icine, ic-kapsama, yapi-icine
   acik cagridan sonra -> erken-birakilan
C acik cagrida      -> erken-birakilan, kapsamda-kalan
   kap dolu iken -> []
D kabiyla birlikte  -> obekte, kap-1, kap-2, kap-3

toplam biten deger: 14
kapsam sonu       6
tasindigi yerde   3
acik cagrida      1
kabiyla birlikte  4

Dört Bölümün Okunuşu

A bölümü üç değer kuruyor (kapsam-1, kapsam-2, kapsam-3) ve iç kapsam kapanır kapanmaz üçü de deftere düşüyor — çıktıda kapsam-3, kapsam-2, kapsam-1 sırasıyla. Bu sıralamaya Sınırlayıcı Ölçüm bölümünde dönülecek. A bölümünün ikinci kısmı gölgelemeyi ölçüyor: aynı ada iki kez bağlanmak (golgelenen, sonra golgeleyen) tek bir bitiş üretmiyor, ikisi ayrı ayrı yaşıyor ve ikisi de kapsam sonunda bitiyor. Gölgeleme anında alınan ölçüm boş çıkıyor çünkü henüz hiçbir şey bitmedi; gölgeleme yalnız yeni bir bağlama kuruyor, eskisinin ömrünü kısaltmıyor. Programlama Temelleri kursu (M01/K02) gölgelemeyi kavram olarak kurmuştu ve Değişkenler ve Değişmezlik dersi (M10/K01) onu tip değiştirebilme yönüyle derinleştirmişti; buradaki pay dar: gölgeleme bir bitiş değildir.

B bölümü aynı sınıfa düşen üç ayrı taşıma biçimini gösteriyor: bir değerin işleve geçirilmesi (islev-icine), bir iç kapsama yeniden bağlanması (ic-kapsama) ve bir yapının alanına konması (yapi-icine). Üçü de dış kapsam kapanmadan önce bitiyor, çünkü sahiplik her seferinde daha dar bir kapsama taşınıyor ve o dar kapsam önce kapanıyor. yut işlevi ve Kutu yapısı bu üç biçimi somutlaştırmak için çekirdeğe eklendi: yut bir değeri parametre olarak alıp hiçbir şey döndürmeden kapsamının sonunda bırakıyor, Kutu ise bir değeri kendi alanına yerleştirip o alanın sahibinin artık Kutu örneği olduğunu gösteriyor. İkisi de dilin bir parçası değil, üç taşıma biçimini yan yana koymak için yazılmış küçük yardımcılar. Bu üç biçimin ayrıntısına Sahiplik ve Taşıma dersinde girilecek; burada yalnız üçünün de aynı sınıfa (taşındığı yerde) girdiği görülüyor.

C bölümü tek bir örnekle nadir bir sınıfı gösteriyor: aynı kapsamdaki iki değerden biri (erken-birakilan) bir çağrıyla kapsamı bitmeden bırakılıyor, öbürü (kapsamda-kalan) normal kapsam sonuna kadar bekliyor. On dört değerden yalnız biri bu yolla bitiyor — açık çağrı, kapsamın kendi akışını beklemeden bir bitişi öne çekiyor.

D bölümü dört değeri tek bir kapta topluyor: bir büyüyen dizi üç eleman tutuyor (kap-1, kap-2, kap-3) ve öbeğe alınmış tekil bir değer (obekte) aynı kapsamda duruyor. Kap dolu iken defter boş çıkıyor — kap kapanmadan hiçbir eleman bitmiyor. Kap kapandığında dört değer de aynı anda deftere düşüyor, ama kendi aralarındaki sırayı koruyorlar: obekte listeden önce bitiyor (bildirimin tersi), listenin kendi elemanları ise eklendikleri sırayla bitiyor.

SK2. Büyüyen dizi içindeki elemanların bırakılma sırası (ekleme sırasıyla, indeks küçükten büyüğe) dilin kendi güvencesidir; bu davranış çekirdeğin seçtiği bir örnek değil, standart kütüphanenin tanımladığı bir kuraldır.

Yığıt, Öbek ve Tek Sahip

Bellek düzeninin kendisi bu kursun konusu değil — Bilgisayarlar Nasıl Çalışır kursu (M01/K01) ve Kaçış Çözümlemesi dersi (M09/K05) yığıt ve öbek ayrımını kavram olarak zaten kurmuştu, bu üçüncü karşılaşma bir kuram tekrarı değil. Buradaki pay dar ve iki cümleyle özetlenebilir: ayrım tipte görünür ve her değerin tek bir sahibi var.

obekte değişkeni bunu somutlaştırıyor. Sıradan bir Iz değeri (örneğin kapsam-1) doğrudan kendi bağlandığı yerde duruyor; Box::new(Iz::yeni("obekte", &defter)) ise değeri öbeğe yerleştiriyor ve bu yerleştirme kodda görünüyor — tipin kendisi Box<Iz>, Iz değil. Öbeğe alma konusunun kendisi (özyinelemeli tiplerin nasıl mümkün kıldığı, sahipliğin nasıl davrandığı) ileride Öbekte Tekil Sahiplik dersinde ele alınacak. Burada görülmesi gereken tek şey şu: obekte de tıpkı yığıttaki üç dizi elemanı gibi aynı deftere, aynı Drop mekanizmasıyla düşüyor. Bir değerin nerede yaşadığı (yığıtta mı öbekte mi) ile ne zaman bittiği ayrı sorular — biri tipten okunuyor, öbürü kapsamdan.

“Tek sahip” ifadesi burada bir slogan değil, defterin kendisinden okunan bir gözlem. On dört değerin hiçbiri deftere iki kez düşmüyor — her ad, çıktının tamamında tam olarak bir kez görünüyor. Bir değerin yığıtta mı öbekte mi durduğu değişse bile bu tekillik değişmiyor: obekte de, kap-1 de, kapsam-1 de birer kez bitiyor. Bu gözlem rastgele değil; bir değerin aynı anda iki ayrı yerde sahiplenilmesi mümkün olsaydı, o değer iki kez bitirilmeye çalışılır ve defterde aynı ad iki kez görünürdü. Sıradaki ders bu tekilliğin taşıma sırasında nasıl korunduğunu — sahiplik el değiştirirken eski sahibin neden artık o değere erişemediğini — doğrudan ölçüyor.

Sınırlayıcı Ölçüm: Bitiş Sırası Bildirimin Tersidir

A bölümünün üç değeri bunu en açık gösteren örnek. kapsam-1, kapsam-2, kapsam-3 sırasıyla bildiriliyor, ama iç kapsam kapandığında bitiş sırası tam ters: kapsam-3, kapsam-2, kapsam-1. Bu bir ölçüm aracının kendi seçimi değil, dilin kendi sözü.

SK3. Yerel bir kapsamdaki değerler her zaman bildirimin tersi sırayla bitiyor — bu Rust’ın dil belirtiminin parçası ve gerçekleştirimden gerçekleştirime değişmiyor. Aynı kural D bölümünde de görülüyor: obekte, kendisinden sonra bildirilen listeden önce bitiyor.

Bu sıra rastgele değil; kapsamda açılan en son bağlamanın en önce bitmesi, iç içe geçmiş kaynakların (bir dosyanın açtığı bir bağlantı gibi) her zaman açılış sırasının tersiyle kapanmasını garanti ediyor. Kaynak koda bakan biri, bir kapsamdaki bitiş sırasını koşum yapmadan, yalnız bildirim sırasını tersine çevirerek söyleyebiliyor.

Bu güvence yalnız bir sıralama ayrıntısı değil, bir bağımlılık düzeni de kuruyor. Bir kapsamda ikinci bağlanan bir değer, ilk bağlanan bir değere ait bir kaynağı paylaşıyorsa — örneğin aynı defteri kendi alanında tutuyorsa — bu ikinci değerin ilk değerden önce bitmesi gerekiyor; aksi hâlde bitiş sırasında elindeki kaynak zaten kapanmış olurdu. Bildirim sırasının tersine çevrilmesi tam olarak bunu sağlıyor: en son açılan, elindeki bağımlılıkları hâlâ canlıyken bitiyor. omur.rs çekirdeğinde bu bağımlılık defterin kendisi — her Iz değeri kendi defter alanını tutuyor ve bu alan, Iz değerinin kendisinden sonra değil, tam olarak onunla birlikte bitiyor.

Özet

  • Bir değerin ömrü dört biçimden birinde bitiyor: kapsam sonu, taşındığı yerde, açık çağrıda, kabıyla birlikte; on dört değerin 6’sı kapsam sonunda, 3’ü taşındığı yerde, 1’i açık çağrıda, 4’ü kabıyla birlikte bitti.
  • Gölgeleme bir bitiş değildir; aynı ada yeniden bağlanmak eski bağlamanın ömrünü kısaltmaz.
  • Bir değerin yığıtta mı öbekte mi yaşadığı tipten okunur (Iz ile Box<Iz> farkı); nerede yaşadığı, ne zaman bittiğinden ayrı bir sorudur.
  • Kapsam sonunda biten değerler her zaman bildirimin tersi sırayla bitiyor; bu dilin sözüdür, ölçüm aracının seçimi değil.

Sonraki Adım

Bu derste bir değerin ömrünün nerede bittiği dört sınıfa ayrıldı ve kapsam sonunun kendi sırası ölçüldü. Geriye kalan üç sınıftan en yaygını — taşındığı yerde — henüz yalnız üç örnekle görüldü. Sıradaki ders bu sınıfı derinleştiriyor: sahiplik bir yerden başka bir yere geçtiğinde bitiş noktasına tam olarak ne oluyor ve üç ayrı taşıma biçimi (işleve, iç kapsama, yapı alanına) neden hep aynı sonuca varı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