İçeriğe geç
academia.sh

Ders 10 / 14

Öbekte Tekil Sahiplik

Bir değerin öbekte yaşarken de tek bir sahibi kalabildiği, kabıyla birlikte biten bir değerin kabın kendi sırasına bağlı olduğu ve bu tekilliğin kendine referans veren bir tipi mümkün kıldığı ölçülüyor.

Yayın Güncelleme

Bu derste

  • Öbeğe alınan bir değerin taşınsa bile tek bir sahibi olduğunu koşturarak gösterir
  • Bir kabın içindeki tekil değer ile listedeki değerlerin bitiş sırasını karşılaştırır
  • Öbekte tekil sahipliğin kendine referans veren bir tipi mümkün kıldığını örnekler
İçindekiler

Yapılarda Referans Tutma dersi, bir yapının bir veriyi ya taşıdığını ya ödünç aldığını, iki durumda da tek bir sahibin bulunduğunu ölçerek kapandı. Bu dersin sorusu tam o varsayımı hedef alıyor: bir değerin sahibi birden çok olabilir mi? Yanıtın kendisi henüz gelmiyor — önce sahiplik sayısı değil, sahip olunan değerin nerede durduğu değişiyor. Bu ara adım gerekli, çünkü paylaşımlı sahipliğin nasıl ölçüleceğini anlamak için önce tek sahipli bir değerin öbekte nasıl davrandığını bilmek gerekiyor. Ölçü ekseni değişmiyor: burada da bir değerin nerede bittiği soruluyor, Eşzamanlılık kursundaki (M09/K03) gibi koşan bir birimin nasıl duracağı değil.

Kursun açılış dersi, bir değerin yığıtta mı öbekte mi durduğunu M01/K01 ve M09/K05’in üçüncü tekrarı olarak ele almıştı; bellek kuramı orada kapandı, burada yeniden açılmıyor. Aynı derste dört sınıf kuruldu: bir değer kapsam sonunda, taşındığı yerde, açık bir çağrıyla ya da bir kabıyla birlikte biter. Bu dersin sorduğu şey şu: bir değer öbekte durduğunda, bu dört sınıftan hangisine giriyor ve sahiplik kuralı hâlâ aynı mı kalıyor? Box, bir değeri öbeğe taşıyan ama sahiplik sayısını hiç değiştirmeyen bir kaptır — bu dersin ölçtüğü ilk şey budur.

Kap da Bir Sınıf, Ama İçindeki Tek

“Kabıyla birlikte” bitişi tek bir biçimde gelmiyor. Bir liste birden çok değeri aynı anda taşır; Box ise tam olarak bir değeri taşır. İkisi de kap sınıfına girer, ikisinde de içerik kapla birlikte biter, ama kabın kendi bitiş sırası aynı değildir.

Bu dersin ölçümü de kursun açılışında kurulan Iz/defter düzeneğini kullanıyor: her Iz değeri bırakıldığında kendi adını deftere ekliyor, bunu sağlayan Drop uygulaması burada yeniden yazılmıyor, kesiti alınıyor. Aşağıdaki koşum, aynı kapsam içinde üç öğeli bir listeyle tek bir Box değerini bir arada tutuyor ve kapsam kapanırken defterin aldığı sırayı basıyor.

PS1. — “öbekteki tekil değerin listeden önce bittiği” iddiası, aşağıdaki koşumun bitis sirasi satırıyla doğrulanıyor: obekteki-tekil adı, listenin üç elemanından önce geliyor.

// kap_siniflari.rs — kabiyla birlikte sinifi: liste ve obekteki tekil deger
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 mut liste = Vec::new();
        liste.push(Iz::yeni("liste-1", &defter));
        liste.push(Iz::yeni("liste-2", &defter));
        liste.push(Iz::yeni("liste-3", &defter));
        let _obekteki = Box::new(Iz::yeni("obekteki-tekil", &defter));
        println!("kap dolu iken defter uzunlugu: {}", defter.borrow().len());
    }
    let kayit = defter.borrow();
    println!("bitis sirasi: {}", kayit.join(", "));
}
kap dolu iken defter uzunlugu: 0
bitis sirasi: obekteki-tekil, liste-1, liste-2, liste-3

Kapsam kapanırken bağlamalar en son bağlanandan başlayarak biter: _obekteki, listeden sonra bağlandığı için ilk biten o oluyor. Ardından liste bitiyor, ama listenin kendi içindeki üç Iz değeri ekledikleri sırayla bitiyor. Bağlamaların bitiş sırası ile bir kabın içindeki elemanların bitiş sırası aynı kural değildir — biri bildirimin tersini, öbürü ekleme sırasını izler; Box tek elemanlı bir kap olduğu için bu ikinci kural onda görünmez, yalnızca birinci kural işler.

Öbekte Durmak Kullanım Biçimini Değiştirmez

Bir değerin öbekte durması, o değere nasıl erişildiğini değiştirmiyor. Bir Box içindeki yapının alanlarına ve yöntemlerine, sanki değer doğrudan orada duruyormuş gibi erişilir; aradaki kabı açmak için ayrı bir adım yazılmaz.

// kutu_erisimi.rs — box uzerinden erisim, deger dogrudan oradaymis gibi calisir
struct Nokta {
    x: i32,
    y: i32,
}

fn main() {
    let nokta = Box::new(Nokta { x: 3, y: 4 });
    println!("x: {}, y: {}", nokta.x, nokta.y);
    let toplam = nokta.x + nokta.y;
    println!("toplam: {}", toplam);
}
x: 3, y: 4
toplam: 7

nokta.x ve nokta.y satırları, noktanın tipi Nokta olsaydı da Box<Nokta> olsaydı da aynı yazılır. Aşağıdaki koşum bunu doğrudan karşılaştırıyor: aynı Nokta değeri önce öbeğe alınmadan, sonra öbeğe alınarak okunuyor.

// erisim_karsilastirmasi.rs — box'lu ve box'suz erisim ayni satirla yaziliyor
struct Nokta {
    x: i32,
    y: i32,
}

fn main() {
    let dogrudan = Nokta { x: 1, y: 2 };
    let obekteki = Box::new(Nokta { x: 1, y: 2 });
    println!("dogrudan: x={}, y={}", dogrudan.x, dogrudan.y);
    println!("obekteki: x={}, y={}", obekteki.x, obekteki.y);
}
dogrudan: x=1, y=2
obekteki: x=1, y=2

İki satır arasındaki tek fark değerin nasıl kurulduğu (Nokta { ... } ile Box::new(Nokta { ... })); alan okuma satırları birbirinin aynısı. Öbekte durmak yalnızca değerin nerede tutulduğunu belirler; değerin okunma ve kullanılma biçimini değiştirmez. Bu, ilerleyen derslerde paylaşımlı kapların da sürdüreceği bir düzendir: kabın türü ne olursa olsun, içindeki değere erişim hep aynı satırla yazılır.

Öbeğe Almak Sahipliği Çoğaltmaz

Sahiplik ve Taşıma dersi, işleve taşınan, iç kapsama taşınan ve bir yapının alanına taşınan değerin bitiş noktasını taşıdığını ama sayısını hiç değiştirmediğini ölçmüştü. Aynı ilke, öbekteki bir değer için de geçerlidir: bir Box değeri kaç kez el değiştirirse değiştirsin, taşıdığı Iz yalnızca bir kez biter.

Aşağıdaki koşum, tek bir Box<Iz> değerini iki ayrı işlev çağrısından geçiriyor ve son sahibin elinde bırakıyor.

PS2. — “üç el değiştirmeden sonra bile tek bir bitiş kaydının oluştuğu” iddiası, aşağıdaki koşumun toplam kayit sayisi: 1 satırıyla doğrulanıyor.

// kutu_tasima.rs — box'a alinan bir degerin tasinmasi tek sahiplik sayisini 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 baska_kapsama(alinan: Box<Iz>) -> Box<Iz> {
    // Sahiplik burada devraliniyor ve ayni deger geri donduruluyor.
    alinan
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    {
        let ilk = Box::new(Iz::yeni("tek-sahip", &defter));
        let ikinci = baska_kapsama(ilk);
        let ucuncu = baska_kapsama(ikinci);
        println!("uc tasimadan sonra defter uzunlugu: {}", defter.borrow().len());
        println!("son sahip: {}", ucuncu.ad);
    }
    let kayit = defter.borrow();
    println!("toplam kayit sayisi: {}", kayit.len());
    println!("kayitlar: {}", kayit.join(", "));
}
uc tasimadan sonra defter uzunlugu: 0
son sahip: tek-sahip
toplam kayit sayisi: 1
kayitlar: tek-sahip

ilk adlı bağlama baska_kapsama işlevine taşınıyor, oradan ikinci adıyla geri dönüyor, sonra yeniden aynı işleve taşınıp ucuncu adıyla dönüyor. Üç bağlama, üç ayrı addır, ama hepsi aynı Iz değerinin farklı zamanlardaki tek sahibidir. ucuncu biterken defter uzunluğunun sıfır olması, önceki iki taşımanın hiçbirinin bir bitiş üretmediğini gösteriyor: yalnızca son sahibin kapsamı kapandığında bir kayıt açılıyor. Defterde tek bir tek-sahip kaydı oluşması, öbeğe almanın sahiplik sayısını çoğaltmadığını doğruluyor — taşıma yalnızca kimin sahip olduğunu güncelliyor, yeni bir kayıt açmıyor.

Bu sonuç ilk bakışta beklenmedik gelmeyebilir, ama karşıt durumu düşünmek farkı netleştirir: sahiplik çoğalsaydı, baska_kapsama her çağrıldığında defterde ayrı bir bitiş kaydı oluşurdu ve toplam üç kayıt görülürdü. Sahiplik ve Taşıma dersinde yığıttaki bir değer için kurulan “tek sahip” kuralı, değer öbeğe taşındığında da bozulmuyor; taşınan şey değerin kendisi değil, yalnızca o değere sahip olma hakkıdır.

Box da Açık Çağrıyla Erken Bitebilir

Öbekteki bir değer, her zaman kabının kapanmasını beklemek zorunda değildir. Ödünç Alma dersinde açık bir drop çağrısının bir değerin ömrünü erken bitirdiği ölçülmüştü; aynı çağrı bir Box üzerinde de çalışır. Bu, “kabıyla birlikte” ile “açık çağrıda” sınıfları arasındaki farkın tipten değil, değerin nasıl bitirildiğinden geldiğini gösteriyor.

PS3. — “erken bırakılan değerin, kapsamda kalan değerden önce bittiği” iddiası, aşağıdaki koşumun bitis sirasi satırıyla doğrulanıyor.

// erken_birakma.rs — bir box degeri acik cagriyla erkenden birakilabilir
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 erken = Box::new(Iz::yeni("erken-birakilan", &defter));
        let kalan = Box::new(Iz::yeni("kapsamda-kalan", &defter));
        drop(erken);
        println!("acik cagridan sonra defter uzunlugu: {}", defter.borrow().len());
        println!("kalan hala okunabilir: {}", kalan.ad);
    }
    let kayit = defter.borrow();
    println!("bitis sirasi: {}", kayit.join(", "));
}
acik cagridan sonra defter uzunlugu: 1
kalan hala okunabilir: kapsamda-kalan
bitis sirasi: erken-birakilan, kapsamda-kalan

erken değeri, kalandan önce bağlanmasına rağmen kapsam sonunu beklemeden bitiyor; kalan ise sıradan kapsam sonu kuralına uyuyor. Aynı tip — Box<Iz> — iki farklı bitiş sınıfına düşebiliyor; sınıfı belirleyen kabın türü değil, kaynakta o değere ne yapıldığıdır.

Bu dersin üç ölçümü bir araya geldiğinde, bir Box değerinin kursun açılışında kurulan dört sınıftan üçüne girebildiği görülüyor: kapsamı doğal biçimde kapanırsa kabıyla birlikte biter (Kap da Bir Sınıf bölümü), bir işleve ya da başka bir bağlamaya taşınırsa bitiş noktası da onunla taşınır (Öbeğe Almak bölümü), açık bir çağrıyla bırakılırsa açık çağrıda biter (bu bölüm). Yalnız kapsam sonu sınıfına giremez, çünkü öbekteki bir değer her zaman ya bir kabın içindedir ya da bir taşımanın konusudur — hiçbir zaman yığıttaki sıradan bir bağlama gibi kendi başına durmaz.

Özyinelemeli Bir Tip: Öbeğin Açtığı Kapı

M01/K03 Veri Yapıları dersi, bağlı liste ve ağaç gibi kendine referans veren yapıları kavram olarak kurdu; bu ders o yapıyı yeniden öğretmiyor, yalnızca öbekte tekil sahipliğin böyle bir tipi mümkün kıldığını ölçüyor.

Bir zincirin her halkasının kendi türünden bir sonraki halkayı doğrudan — bir kap olmadan — taşıdığı bir tanım derleyici tarafından reddediliyor: her halka bir sonrakini doğrudan içerdiğinde, tipin kendisinin ne kadar yer kaplayacağı hiçbir zaman belirlenmiş sayılmıyor, çünkü halka sayısı kaynaktan sabit bir sınırla okunamıyor. Bu, bir işlevin kendini çağırmasından farklı bir sorundur: işlev özyinelemesi çalışma zamanında adım adım ilerler, ama bir tipin tanımı derleme zamanında tek seferde okunmak zorundadır — kendi içinde sonsuza kadar açılan bir tanım bu okumayı bitiremez.

Kuralın en yalın hâli şöyle özetlenebilir: bir Kayit yapısının sonraki adlı alanına doğrudan Kayit tipini yazan bir tanım hiçbir zaman kabul edilmez, çünkü sonraki yine bir Kayittir ve bu tanım kendi içinde asla kapanmaz. Aynı alana Box<Kayit> yazan bir tanım kabul edilir, çünkü Box öbekte duran, kendisi sabit biçimli ayrı bir kaptır — tanımın kendisi Kayitin içine doğrudan bir Kayit değil, sabit biçimli bir kabın kendisini yerleştirir. Kabul edilen yazım, sonraki halkayı da tam olarak bu yolla taşıyor: doğrudan değil bir Box üzerinden ve bu yüzden zincir istediği uzunlukta sürebiliyor.

PS4. — “zincirin bitiş sırasının kurulma sırasıyla aynı ilerlediği” iddiası, aşağıdaki koşumun bitis sirasi: halka-a, halka-b, halka-c satırıyla doğrulanıyor — sıra, bağlamaların bitişinde görülen tersine dönüşü izlemiyor.

// zincir.rs — box'un actigi kapi: kendini icinde barindiran bir tip
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());
    }
}

enum Zincir {
    Bos,
    Dugum(Iz, Box<Zincir>),
}

fn uzunluk(z: &Zincir) -> usize {
    match z {
        Zincir::Bos => 0,
        Zincir::Dugum(iz, sonraki) => {
            println!("gezilen halka: {}", iz.ad);
            1 + uzunluk(sonraki)
        }
    }
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    {
        let zincir = Zincir::Dugum(
            Iz::yeni("halka-a", &defter),
            Box::new(Zincir::Dugum(
                Iz::yeni("halka-b", &defter),
                Box::new(Zincir::Dugum(
                    Iz::yeni("halka-c", &defter),
                    Box::new(Zincir::Bos),
                )),
            )),
        );
        println!("zincir uzunlugu: {}", uzunluk(&zincir));
    }
    let kayit = defter.borrow();
    println!("bitis sirasi: {}", kayit.join(", "));
}
gezilen halka: halka-a
gezilen halka: halka-b
gezilen halka: halka-c
zincir uzunlugu: 3
bitis sirasi: halka-a, halka-b, halka-c

Bu sıra ilk bakışta şaşırtıcı görünebilir, çünkü bu derste önce ölçülen kural bağlamaların son bağlanandan başlayarak bittiğiydi. Ama burada tek bir dış bağlama var — zincir —, üç ayrı bağlama yok. zincir bittiğinde önce kendi Iz alanı biter (halka-a), ardından kendi Box<Zincir> alanı biter; o Box da kendi içindeki değeri bitirirken önce onun Iz alanını bitiriyor (halka-b), sonra kendi Boxunu ve bu böyle sürüyor. Bitiş, dıştan içe doğru ilerleyen tek bir zincirleme çağrı olduğu için kurulma sırasıyla aynı yönde ilerliyor; bu, “Kap da Bir Sınıf” bölümündeki liste örneğinden farklı bir bitiş biçimidir — orada çok sayıda bağımsız bağlama vardı, burada iç içe geçmiş tek bir bağlama zinciri var.

Dört ölçüm bir araya geldiğinde tek bir resim çıkıyor: Box, bir değerin nerede durduğunu değiştiren ama kaç sahibi olduğunu hiç değiştirmeyen bir kaptır. Değere erişim biçimi aynı kalıyor, taşıma bitiş noktasını taşıyor ama sayıyı çoğaltmıyor, açık bir çağrı erken bitirebiliyor ve bu tekil sahiplik sayesinde bir tip kendi türünden bir sonraki halkayı taşıyabiliyor. Sıradaki sorunun zemini de tam burada kuruluyor: bu dört ölçümün hiçbirinde sahiplik sayısı birden fazla olmadı — hep tek bir bağlama, tek bir anda, o değerin sahibiydi.

Özet

  • Bir değer öbekte durduğunda da tek bir sahibi olur; Box bu sayıyı değiştirmez.
  • Bir kabın içindeki tekil değer ile listedeki değerler farklı bitiş sırası kurallarına uyar: biri bildirimin tersini, öbürü ekleme sırasını izler.
  • Öbekte durmak bir değerin okunma ve kullanılma biçimini değiştirmez.
  • Öbekteki bir değer taşınsa bile defterde yalnızca tek bir bitiş kaydı oluşur.
  • Aynı tip, kaynakta yapılana göre “kabıyla birlikte” ya da “açık çağrıda” sınıflarından birine düşebilir.
  • Öbekte tekil sahiplik, kendine referans veren bir tipin derlenebilmesini sağlar; bir zincir tipinin bitiş sırası bu durumda kurulma sırasıyla aynı yönde ilerleyebilir.

Sonraki Adım

Bu derste öbekteki bir değer taşınsa da, erken bırakılsa da, zincirlense de, sahibi hep tek kaldı. Sıradaki ders bu varsayımı kaldırıyor: aynı değeri aynı anda birden çok bağlamanın sahiplenebildiği bir düzenek — ve bu paylaşımın kaynaktan okunabilir bir sayaçla nasıl izlendiği ölçülü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