İçeriğe geç
academia.sh

Ders 11 / 14

Referans Sayımı

Bir Rc değerinin sayacının her kopyada arttığı, her bitişte azaldığı ve değerin ancak sayaç sıfıra inince deftere düştüğü ölçülüyor; bir referans almanın bu sayacı hiç değiştirmediği ve hangi kopyanın bırakıldığının değil kaç kopyanın kaldığının belirleyici olduğu gösteriliyor.

Yayın Güncelleme

Bu derste

  • Bir paylaşılan değerin sayacının her kopyada arttığını, her bitişte azaldığını koşturarak gösterir
  • Sayacın sıfıra inmesiyle değerin deftere düşmesinin aynı ana denk geldiğini doğrular
  • Bir referans almanın sayacı değiştirmediğini, yalnızca kopyalamanın değiştirdiğini örnekle ayırt eder
İçindekiler

Önceki ders, öbekteki bir değerin taşınsa da, erken bırakılsa da, zincirlense de hep tek bir sahibi olduğunu ölçtü. Bu dersin sorusu tam o varsayımı kaldırıyor: bir değerin sahibi birden çok olabilir mi? Yanıt evet — ama bu evet bir kaçamak değil, kaynaktan okunabilen bir sayaçla izlenen, belirlenimci bir paylaşımdır. Sayacın izlediği şey bir değerin bitiş noktasıdır; Eşzamanlılık kursunun (M09/K03) ölçtüğü koşan bir birimin durması bu kursun sorusu değil.

Rc<T>, bir değeri öbeğe koyup kaç bağlamanın o değere ortak eriştiğini sayan bir kaptır. Değer, bu sayaç sıfıra düşünceye kadar yaşar; sayaç sıfıra indiği an, önceki derste görülen tekil sahiplik biter ve değer defterine düşer. Bu ders o sayacı doğrudan okuyarak, her kopyalamada ve her bitişte ne olduğunu, hangi bağlamanın “sahip” sayıldığını ve hangisinin sayılmadığını ölçüyor.

Neden Bir Referans Yetmez

Ödünç Alma dersi, bir referansın ödünç aldığı değerden daha uzun yaşayamayacağını ölçmüştü; Yaşam Süresi Kavramı dersi bu sınırı bir geçerlilik aralığı olarak adlandırdı. Bu sınır, aynı değere birden çok yerden erişmek isteyen ama kaynaktaki tek sahiplik bağlamasının ömrüyle sınırlı kalmak istemeyen bir kullanım için yetersiz kalır: referans, sahibinin kapsamı kapanınca geçersiz olur, kaç yerden okunursa okunsun — sahibinin kendisi hâlâ tektir. Rc, bu sınırı bir kopyanın kendisini de bir sahip yaparak aşıyor: artık ödünç alan bir taraf değil, kendi başına bir sahip olan bir kopya söz konusu ve bu kopya orijinal bağlamadan bağımsız olarak kendi kapsamının sonuna kadar yaşayabiliyor.

Sahiplik Sayılabilir Hâle Geliyor

Bir değer Rc::new ile öbeğe konduğunda sayaç bire kurulur. Rc::clone çağrıldığında yeni bir kopya üretilir, sayaç bir artar; kopyalardan biri bittiğinde sayaç bir azalır. Bu kopyalar birbirinin aynı değerine erişir — her biri kendi ayrı bir değer taşımaz, hepsi tek bir Izyi paylaşır.

PS5. — “sayacın her kopyada arttığı ve her bitişte azaldığı” iddiası, aşağıdaki koşumun 1, 2, 3, 2 sırasıyla basılan sayaç değerleriyle doğrulanıyor.

// referans_sayaci.rs — Rc'nin sayaci her kopyada artiyor, her bitiste azaliyor
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());
    }
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    {
        let paylasilan: Rc<Iz> = Rc::new(Iz::yeni("paylasilan", &defter));
        println!("kurulunca sayac: {}", Rc::strong_count(&paylasilan));

        let ikinci = Rc::clone(&paylasilan);
        println!("bir kopyadan sonra sayac: {}", Rc::strong_count(&paylasilan));

        {
            let _ucuncu = Rc::clone(&paylasilan);
            println!("iki kopyadan sonra sayac: {}", Rc::strong_count(&paylasilan));
            println!("ic kapsam biterken defter uzunlugu: {}", defter.borrow().len());
        }
        println!("ic kapsam bittikten sonra sayac: {}", Rc::strong_count(&paylasilan));
        println!("ikinci kopya hala okunabilir: {}", ikinci.ad);
    }
    let kayit = defter.borrow();
    println!("defterdeki kayit sayisi: {}", kayit.len());
    println!("kayitlar: {}", kayit.join(", "));
}
kurulunca sayac: 1
bir kopyadan sonra sayac: 2
iki kopyadan sonra sayac: 3
ic kapsam biterken defter uzunlugu: 0
ic kapsam bittikten sonra sayac: 2
ikinci kopya hala okunabilir: paylasilan
defterdeki kayit sayisi: 1
kayitlar: paylasilan

İç kapsam kapanırken _ucuncu bitiyor, ama defter uzunluğu sıfır kalıyor: _ucuncunun bitişi yalnızca sayacı ikiye indiriyor, Iz değerinin kendisini bitirmiyor. paylasilan ve ikinci hâlâ aynı değere erişebiliyor — ikinci.ad okunduğunda hâlâ paylasilan metnini veriyor, çünkü ikisi aynı değeri paylaşıyor. Dış kapsam kapandığında geri kalan iki kopya da bitiyor, sayaç sıfıra iniyor ve ancak o an defterde tek bir kayıt beliriyor. Üç kopyaya rağmen defterde tek kayıt olması, paylaşımın değeri çoğaltmadığını, yalnızca ona erişen yol sayısını çoğalttığını gösteriyor.

Kopyalamak Veri Kopyalamak Değildir

Yukarıdaki koşumda Iz::yeni yalnızca bir kez çağrılıyor — paylasilan kurulurken. ikinci ve _ucuncu, Rc::clone(&paylasilan) ile üretiliyor; bu çağrı ne Iz::yeniyi yeniden çalıştırıyor ne de ad alanını yeniden kuruyor. ikinci.adin hâlâ paylasilan metnini vermesi bir rastlantı değil: ikinci ayrı bir Iz değeri taşımıyor, paylasilan ile aynı değere bakan ikinci bir erişim yolu taşıyor. Bu, bir dizginin ya da bir listenin .clone() çağrısından farklıdır — onlarda içerik gerçekten kopyalanır ve sonradan biri değiştirilse öbürü değişmez. Rc::clone, adındaki fiile rağmen, taşıdığı değeri değil yalnızca sayacı ve erişimi çoğaltıyor. Bu fark, ilerleyen bir derste paylaşılan verinin değiştirilmesi gündeme geldiğinde önemli sonuçlar doğuruyor: iki kopyadan biri üzerinden yapılan bir değişiklik, öbür kopyadan da görülebilir hâle gelir, çünkü aslında değişen tek bir değerdir.

Her Değerin Kendi Sayacı Vardır

Bir programda birden çok paylaşılan değer varsa, her birinin sayacı ayrıdır. Bir değerin kopyalanması ya da bitmesi, başka bir değerin sayacına dokunmaz.

// bagimsiz_sayaclar.rs — farkli degerlerin sayaclari birbirinden bagimsizdir
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());
    }
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    let birinci: Rc<Iz> = Rc::new(Iz::yeni("birinci-deger", &defter));
    let ikinci: Rc<Iz> = Rc::new(Iz::yeni("ikinci-deger", &defter));

    let birinci_kopya = Rc::clone(&birinci);
    println!("birinci sayac: {}", Rc::strong_count(&birinci));
    println!("ikinci sayac: {}", Rc::strong_count(&ikinci));

    drop(birinci_kopya);
    drop(birinci);
    println!("defter (yalniz birinci bitti): {}", defter.borrow().join(", "));

    drop(ikinci);
    println!("defter (ikisi de bitti): {}", defter.borrow().join(", "));
}
birinci sayac: 2
ikinci sayac: 1
defter (yalniz birinci bitti): birinci-deger
defter (ikisi de bitti): birinci-deger, ikinci-deger

birinci bir kez kopyalandığı için sayacı iki, ikinci hiç kopyalanmadığı için sayacı bir. birincinin iki kopyası da bittiğinde ikincinin sayacı hâlâ bir olarak kalıyor — ikinci bu değişiklikten habersiz. Aynı defter her iki değer için de ortak kullanılıyor (bu bir kayıt aracı, sayılan değerin kendisi değil), ama her Iznin kendi Rc sarmalayıcısı kendi sayacını tutuyor. Bir programda kaç ayrı paylaşılan değer varsa, o kadar ayrı ve birbirinden bağımsız sayaç vardır.

Paylaşım Sahiplik Kuralını İhlal Etmiyor, Genelliyor

Önceki derste kurulan “tek sahip” kuralı burada bozulmuyor, genelleniyor: sahip artık tek bir bağlama değil, sayaç sıfıra düşene kadar aktif kalan kopyaların kümesidir. Sayaç sıfıra indiği an kesin olarak bellidir ve bu an, koşumda her zaman aynı sırayla üretilen bir kayıttır — bir sonraki adımın ne yapacağına bağlı bir tahmin değil, kaynaktaki kopya sayısının doğrudan sonucudur. Bu, tek iş parçacığında çalışan sıradan bir tam sayı gibi davranır: Rc::clone çağrısı bir kopya üretip sayacı artırıyor, kopyanın bitişi sayacı azaltıyor ve iki işlem de aynı yerden — aynı kapsamdan — yürüyor.

Sayaç, tek tek adlandırılmış bağlamalarla sınırlı değildir; bir döngüde üretilen kopyalar da aynı kurala uyar. Aşağıdaki koşum beş kopyayı bir listeye topluyor, sonra listeyi tek seferde boşaltıyor.

// toplu_kopyalama.rs — bir donguyle uretilen kopyalar temizlenince sayac baslangica doner
use std::rc::Rc;

fn main() {
    let paylasilan: Rc<i32> = Rc::new(7);
    let mut kopyalar: Vec<Rc<i32>> = Vec::new();
    for _ in 0..5 {
        kopyalar.push(Rc::clone(&paylasilan));
    }
    println!("bes kopyadan sonra sayac: {}", Rc::strong_count(&paylasilan));

    kopyalar.clear();
    println!("kopyalar temizlenince sayac: {}", Rc::strong_count(&paylasilan));
}
bes kopyadan sonra sayac: 6
kopyalar temizlenince sayac: 1

Beş kopya listeye eklenince sayaç altıya çıkıyor — ilk kurulan paylasilan da bir kopya sayılıyor. Liste tek seferde boşaltıldığında beş kopya art arda bitiyor ve sayaç bire dönüyor; paylasilan kendisi hâlâ elde olduğu için değer henüz bitmiyor. Bu örnek Iz düzeneğini kullanmıyor, çünkü burada ölçülen şey bir bitiş kaydı değil, sayacın kendisinin toplu işlemler altında da aynı kuralı izlediği.

Sayılan Şey Sahiptir, Ödünç Değil

Sayaç, yalnızca Rc::clone çağrılarını sayar. Paylaşılan değere bir referans almak — onu okumak için ödünç almak — sayacı hiç değiştirmez, çünkü ödünç alan taraf değerin sahibi değildir, yalnızca ona geçici olarak erişir.

PS6. — “bir referans almanın sayacı değiştirmediği” iddiası, aşağıdaki koşumda kurulunca sayac ile odunc alip okuduktan sonra sayac satırlarının aynı değeri (1) vermesiyle doğrulanıyor.

// odunc_sayaca_girmez.rs — bir referans almak Rc sayacini degistirmiyor
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());
    }
}

fn oku(deger: &Iz) -> usize {
    deger.ad.len()
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    let paylasilan: Rc<Iz> = Rc::new(Iz::yeni("olcum-degeri", &defter));
    println!("kurulunca sayac: {}", Rc::strong_count(&paylasilan));

    let uzunluk = oku(&paylasilan);
    println!(
        "odunc alip okuduktan sonra sayac: {} (uzunluk {})",
        Rc::strong_count(&paylasilan),
        uzunluk
    );

    let kopya = Rc::clone(&paylasilan);
    println!("kopyaladiktan sonra sayac: {}", Rc::strong_count(&paylasilan));

    drop(kopya);
    println!("kopyayi birakinca sayac: {}", Rc::strong_count(&paylasilan));
}
kurulunca sayac: 1
odunc alip okuduktan sonra sayac: 1 (uzunluk 12)
kopyaladiktan sonra sayac: 2
kopyayi birakinca sayac: 1

oku işlevi &Iz alıyor, Rc<Iz>nin kendisini değil — bu yüzden çağrı sayacı hiç etkilemiyor. Sayaç yalnızca Rc::clone çağrısıyla ikiye çıkıyor ve kopya açıkça bırakıldığında yeniden bire dönüyor. Ödünç Alma dersinde kurulan “ödünç alınan değer bitmiyor” ilkesi burada da geçerli: bir referans, değerin sahiplik defterine hiç girmiyor; yalnızca kopyalar sahip sayılıyor, ödünç alanlar sayılmıyor.

Bu ayrım küçük görünse de yanlış anlaşılmaya açıktır, çünkü Rc<Iz> üzerinden bir işleve &paylasilan geçirmek ile Rc::clone(&paylasilan) geçirmek yüzeysel olarak benzer görünür — ikisi de bir & işareti taşıyabilir. Aradaki fark şudur: oku(&paylasilan) çağrısı Rc<Iz>yi ödünç alıp içindeki &Izye otomatik olarak iniyor, hiçbir yeni sahip kurmuyor; Rc::clone(&paylasilan) ise açıkça yeni bir sahip kopyası üretiyor. Kod okunurken bu iki niyeti ayırt etmenin yolu, çağrının clone adını taşıyıp taşımadığına bakmaktır — sayaç yalnızca o adı taşıyan çağrıya tepki verir.

Sıra Değil, Sayı Belirleyici

Önceki derste bir kabın içindekilerin bitiş sırası ölçülmüştü — hangi değerin önce, hangisinin sonra bittiği belirleyiciydi. Referans sayımında durum farklıdır: hangi kopyanın hangi sırayla bırakıldığı değeri bitirmez, yalnızca kaç kopyanın kaldığı belirler.

PS7. — “değerin, hangi kopyanın değil son kalan kopyanın bırakılmasıyla bittiği” iddiası, aşağıdaki koşumda dört kopyadan ilk kurulanın ortadan bırakılmasına rağmen defterin boş kalmasıyla, yalnızca son kopya giderken dolmasıyla doğrulanıyor.

// sira_onemsiz.rs — Rc'de hangi kopyanin birakildigi degil, kac kopya kaldigi belirleyici
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());
    }
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    let paylasilan: Rc<Iz> = Rc::new(Iz::yeni("son-kopya-birakilinca-biten", &defter));
    let a = Rc::clone(&paylasilan);
    let b = Rc::clone(&paylasilan);
    let c = Rc::clone(&paylasilan);
    println!("dort kopyadan sonra sayac: {}", Rc::strong_count(&a));

    drop(b);
    println!("ortadaki kopya birakilinca sayac: {}", Rc::strong_count(&a));
    println!("defter hala bos mu: {}", defter.borrow().is_empty());

    drop(paylasilan);
    println!("ilk kurulan kopya birakilinca sayac: {}", Rc::strong_count(&a));
    println!("defter hala bos mu: {}", defter.borrow().is_empty());

    drop(a);
    drop(c);
    let kayit = defter.borrow();
    println!("son kopya da birakilinca kayit: {}", kayit.join(", "));
}
dort kopyadan sonra sayac: 4
ortadaki kopya birakilinca sayac: 3
defter hala bos mu: true
ilk kurulan kopya birakilinca sayac: 2
defter hala bos mu: true
son kopya da birakilinca kayit: son-kopya-birakilinca-biten

Dört kopyadan b bırakılıyor, sonra ilk kurulan paylasilan bırakılıyor — kuruluş sırasına göre ne ilk ne son olan bir kopya, sonra da en eski kopyanın kendisi gidiyor. İki bırakmadan sonra bile defter boş kalıyor, çünkü geriye hâlâ iki kopya (a ve c) var. Değer ancak bu ikisi de bırakıldığında biter. Bir önceki derste bir kabın içindekilerin bitişi hangi sırayla eklendiğine bağlıydı; burada bitiş yalnızca kaç kopyanın kaldığına bağlı — hangi kopyanın adı, hangi sırayla bırakıldığı sonucu değiştirmiyor. Bu fark, iki kap türü arasındaki temel ayrımı özetliyor: bir liste ya da bir Box, tek bir sahiplik zincirinin konumunu kaydeder ve bu yüzden sıraya duyarlıdır; bir Rc, sahiplik zincirinin uzunluğunu kaydeder ve bu yüzden yalnızca sayıya duyarlıdır.

Yukarıdaki koşumlarda kullanılan açık drop çağrısı, Öbekte Tekil Sahiplik dersinde görülen açık çağrı fikrini burada da sürdürüyor, ama sonucu farklıdır. Orada bir Box üzerinde açık çağrı değerin kendisini erkenden bitiriyordu — tek sahip olduğu için başka seçenek yoktu. Burada aynı çağrı yalnızca bir kopyayı bırakıyor; değerin kendisi, geriye başka kopya kaldığı sürece bitmiyor. Aynı drop işlevi, tek sahiplikte kesin bir bitiş, paylaşımlı sahiplikte ise koşullu bir azaltma anlamına geliyor — anlam, çağrıldığı değerin sahiplik biçimine göre değişiyor.

Dört koşum bir araya geldiğinde, referans sayımının sahiplik kuralını nasıl genellediği netleşiyor. Sayaç her zaman tek bir yerden, tek bir sırayla güncelleniyor; kopyalamak veriyi değil erişimi çoğaltıyor; bir referans hiçbir zaman bu sayıma girmiyor; ve bitişi belirleyen şey kopyaların kimliği değil miktarıdır. Bu dört özellik birlikte, hâlâ belirlenimci ve kaynaktan okunabilir bir bitiş noktası sağlıyor — sayaç sıfıra indiği an, tıpkı önceki derste tek sahipli bir değerin kapsam sonu gibi, koşumda kesin ve tekrarlanır biçimde görülüyor.

Özet

  • Rc<T>, bir değeri öbeğe koyup ona erişen kopya sayısını sayan bir kaptır.
  • Sayaç her Rc::clone çağrısında artar, her kopyanın bitişinde azalır.
  • Değer, ancak sayaç sıfıra indiğinde defterine düşer — ömrü kabıyla birlikte, son kalan kopyanın bitişinde biter; bu an kaynaktan okunabilir ve koşumda her zaman aynı sırayla üretilir.
  • Bir referans almak sayacı değiştirmez; yalnızca kopyalamak değiştirir.
  • Değerin bitişi hangi kopyanın bırakıldığına değil, kaç kopyanın kaldığına bağlıdır.
  • Rc::clone taşınan değeri değil, sayacı ve erişimi çoğaltır; kopyalar aynı değere bakar.

Sonraki Adım

Bu derste ölçülen sayaç, tek bir bağlamda, sıradan bir tam sayı gibi güncellendi: her kopyalama ve her bitiş aynı kapsamdan, aynı sırayla yürüdü. Sıradaki ders aynı sayma işini yapan ama sayacın kendisini farklı bir biçimde güncelleyen bir türü ölçüyor — davranış aynı kalıyor, yalnızca sayacın güncellenme biçimi değişiyor; hangi bağlamda bu farkın gerektiği, bu dersin kapsamının dışında kalı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