İçeriğe geç
academia.sh

Ders 09 / 14

Yapılarda Referans Tutma

Bir yapının alanında referans tutmanın yapının kendi ömrüne getirdiği koşul ölçülüyor: yapı, referans verdiği veriden uzun yaşayamaz. Aynı veri bir yapıya taşınarak ve ödünç verilerek tutuluyor; taşınan alanın yapıyla birlikte bittiği, ödünç verilen verinin yapıdan sonra bittiği koşturularak karşılaştırılıyor.

Yayın Güncelleme

Bu derste

  • Bir yapının alanında referans tutmanın yaşam süresi parametresi gerektirdiğini açıklar
  • Taşınan bir alanla ödünç alınan bir alanın bitiş noktasını koşturarak karşılaştırır
  • Bir yapının referans verdiği veriden uzun yaşayamayacağını redd-kabul çiftiyle gösterir
İçindekiler

Önceki ders çıkarım kurallarının işlev parametreleri arasında kapsadığı ve kapsamadığı imzaları ölçtü. Ama açık açıklama yalnızca işlev imzalarında değil, bir yapının kendi tanımında da gerekebilir: bir yapının alanı bir referans tutuyorsa, yapının kendisi bu referansın ömrüne bağlı hâle geliyor. Bu ders bu koşulu, aynı veriyi bir yapıya iki farklı yoldan — taşıyarak ve ödünç vererek — tutan iki kurguyu karşılaştırarak ölçüyor.

Eşzamanlılık kursunun (M09/K03) sorduğu soru burada da farklı: orada koşan bir birimin durması ölçülüyordu, burada bir yapının içindeki bir alanın bitiş noktası ölçülüyor. Yığıt ve Öbek dersinde kurulan dört bitiş sınıfı burada yeniden karşımıza çıkıyor, ama bu kez iki farklı alan türü üzerinden karşılaştırılıyor.

Bu konu boyunca ölçülen şey hep aynı soruydu: bir referansın geçerlilik aralığı nereden okunuyor. Yaşam Süresi Kavramı dersi bu aralığı bir referans için, Açık Yaşam Süresi Açıklamaları ve Yaşam Süresi Çıkarım Kuralları dersleri bunu bir işlev imzası için ölçtü. Bu son ders aynı soruyu bir yapı tanımına taşıyor: bir yapının kendi ömrü, taşıdığı bir referansın ömrüyle nasıl ilişkileniyor.

İki Yol: Taşımak ve Ödünç Almak

Bir yapı, bir alanına bir değer koymak istediğinde iki seçeneği var: değeri taşıyabilir (alan sahip olur) ya da değere bir referans tutabilir (alan yalnızca ödünç alır). Sahiplik ve Taşıma dersinde görülen taşıma kuralı ilk seçenekte geçerli — alan, kendi başına sahip olunan sıradan bir değer gibi davranıyor. İkinci seçenek yeni bir soru açıyor: yapının kendisi ne kadar yaşayabilir, referans verdiği veriden daha uzun yaşayabilir mi?

Cevap hayır ve bu, derleyicinin bir yapı tanımında zorunlu kıldığı bir kural. Bir yapının alanı bir referans tutuyorsa, yapının kendi tanımı bir yaşam süresi parametresi taşımak zorunda — bu parametre, yapının kendisinin en fazla ne kadar yaşayabileceğini, referans verdiği veriyle ilişkilendiriyor.

Bu, önceki iki derste işlev imzaları için kurulan kuralın yapı tanımlarındaki karşılığı. Orada bir işlevin dönen referansı hangi girdiye bağlı olduğunu söylemek zorundaydı; burada bir yapının kendisi, taşıdığı referansın hangi kaynağa bağlı olduğunu söylemek zorunda. Fark, ilişkinin nerede kurulduğu: bir işlev çağrısının ömründe değil, bir yapı örneğinin ömründe. Bu örnek ne kadar uzun yaşarsa yaşasın — bir değişkende, bir başka yapının alanında, bir koleksiyonun içinde — taşıdığı referansın kaynağından daha uzun yaşayamayacağı garantisi değişmiyor.

Ölçüm: Taşınan Alan Yapıyla Birlikte Bitiyor, Ödünç Alınan Veri Yapıdan Sonra Bitiyor

Aşağıdaki ölçüm iki yapı tanımlıyor: SahipYapi bir Iz değerine sahip oluyor, OduncYapi bir Iz değerine yalnızca referans tutuyor ve bu yüzden <'a> parametresi taşıyor. İki yapı da aynı çekirdek (Iz, Defter, Drop) üzerinde çalışıyor; ölçülen fark yalnızca alanın taşınıp taşınmadığında.

// yapida_referans.rs — yapida tasinan alan ile odunc alinan alanin bitis noktasi karsilastirmasi.

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 okunan(defter: &Defter) -> Vec<String> {
    defter.borrow().clone()
}

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

struct SahipYapi {
    icerik: Iz,
}

struct OduncYapi<'a> {
    icerik: &'a Iz,
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));

    // Tasinan alan: yapi bitince biter.
    let s0 = okunan(&defter).len();
    {
        let tasinan = Iz::yeni("tasinan-alan", &defter);
        let yapi = SahipYapi { icerik: tasinan };
        println!("SahipYapi icindeki alan -> {}", yapi.icerik.ad);
        println!("SahipYapi ayaktayken -> [{}]", yeni_olanlar(&defter, s0));
    }
    println!("SahipYapi bitince    -> {}", yeni_olanlar(&defter, s0));

    // Odunc alinan alan: yapi biter, veri hala yasar.
    let o0 = okunan(&defter).len();
    {
        let odunc_veri = Iz::yeni("odunc-veri", &defter);
        {
            let yapi = OduncYapi { icerik: &odunc_veri };
            println!("OduncYapi icinde     -> {}", yapi.icerik.ad);
        }
        println!("OduncYapi bitince    -> [{}]", yeni_olanlar(&defter, o0));
    }
    println!("odunc-veri bitince   -> {}", yeni_olanlar(&defter, o0));
}
SahipYapi icindeki alan -> tasinan-alan
SahipYapi ayaktayken -> []
SahipYapi bitince    -> tasinan-alan
OduncYapi icinde     -> odunc-veri
OduncYapi bitince    -> []
odunc-veri bitince   -> odunc-veri

SahipYapi bölümünde tasinan değeri yapi’nin alanına taşınıyor ve o andan sonra kendi başına bir bağlaması kalmıyor — yalnızca yapi.icerik üzerinden erişilebiliyor. yapi ayaktayken defter boş; yapi kapsamı kapanınca tasinan-alan deftere düşüyor. Bu, Sahiplik ve Taşıma dersinde ölçülen “yapı alanına taşınan değer” kalıbının bir tekrarı: alan, yapının kendisiyle birlikte bitiyor.

OduncYapi bölümünde ise odunc_veri kendi bağlamasında kalıyor ve yapi yalnızca ona bir referans tutuyor. yapi’nin kendi iç kapsamı kapandığında defter hâlâ boş — OduncYapi örneğinin bitişi hiçbir kayıt üretmiyor, çünkü &'a Iz Drop uygulamıyor. odunc_veri kendisi ancak kendi kapsamı (dıştaki blok) kapanınca deftere düşüyor — bu da OduncYapi örneğinin bitişinden sonra.

İki bölümün ortak noktası, ikisinde de yalnızca bir kaydın oluşması: SahipYapi bölümünde tek kayıt tasinan-alan, OduncYapi bölümünde tek kayıt odunc-veri. OduncYapi’nin kendisi hiçbir zaman deftere düşmüyor — çünkü yapının kendisi Drop uygulamıyor, yalnızca içindeki referans aracılığıyla başka bir yerdeki Iz değerine bakıyor. Bu, referans tutan bir yapının kendi bitişinin gözlemlenebilir bir yan etkisi olmadığını gösteriyor; gözlemlenebilir olan yalnızca referans verdiği verinin bitişi ve bu bitiş hiçbir zaman yapının kendi bitişinden önce gelmiyor — en erken, ikisinin aynı kapsamda birlikte bitmesiyle çakışabiliyor.

YS9. Bir yapının alanına taşınan bir değer, yapının kendisiyle birlikte biter. Bir yapının alanında tutulan bir referansın verisi ise yapıdan sonra biter — yapının kendisi hiçbir zaman kendi verisinden uzun yaşamıyor, ama referans verilen veri yapıdan daha uzun yaşayabilir ve genellikle de yaşıyor.

Yapının Kendi Yöntemi Yine Çıkarımla Yazılıyor

OduncYapi<'a> bir yaşam süresi parametresi taşısa da, üzerine yazılan yöntemler bu parametreyi mutlaka tekrarlamak zorunda değil. Önceki ders kurulan çıkarım kuralları burada da geçerli: &self alan bir yöntemin dönen referansı, üçüncü kural gereği self’in kendi ömrüne bağlanıyor ve bu, 'a’nın ayrıca yazılmasını gereksiz kılıyor. Bu, iki dersin birbirinden bağımsız olmadığını, aynı kuralların farklı bağlamlarda tekrar tekrar uygulandığını gösteren somut bir örnek.

// yontem_cikarimi.rs — omur parametresi tasiyan yapinin kendi yontemi hala cikarimla yaziliyor.

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 OduncYapi<'a> {
    icerik: &'a Iz,
}

impl<'a> OduncYapi<'a> {
    // Aciklamasiz: cikarimin ucuncu kurali (self) burada da gecerli.
    fn ad(&self) -> &str {
        self.icerik.ad.as_str()
    }
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    let veri = Iz::yeni("paylasilan-veri", &defter);
    let yapi = OduncYapi { icerik: &veri };
    println!("{}", yapi.ad());
}
paylasilan-veri

impl<'a> OduncYapi<'a> bloğu 'a’yı tanıtmak zorunda — yapı tanımı onu gerektiriyor — ama fn ad(&self) -> &str imzası kendi başına hiçbir açıklama taşımıyor. Derleyici burada da önceki dersteki gibi davranıyor: &self tek referans girdisi olduğu için üçüncü kural uygulanıyor ve dönen &str, self’in ömrüne bağlanıyor. self’in kendisi OduncYapi<'a>’ya bir referans olduğundan, dönen dilim de dolaylı olarak 'a’nın sınırları içinde kalıyor — ama bu ilişki ad yönteminin kendi imzasında hiç yazılmıyor, çünkü çıkarım onu zaten karşılıyor.

Bu ayrım, 'a’nın nerede tanımlandığı ile nerede kullanıldığı arasındaki farkı gösteriyor. 'a, yapının tanımında ve impl bloğunun başlığında görünmek zorunda, çünkü alanın kendisi ona bağlı. Ama bir yöntemin dönen değeri her zaman self üzerinden erişiliyorsa, o yöntemin kendi imzası 'a’yı tekrar yazmadan da doğru davranıyor — çıkarım kuralı burada tek bir girdi (self) üzerinden çalışıyor, 'a’nın kendisiyle hiç ilgilenmiyor. 'a’nın açıkça yazılması gereken yer, bir yöntemin dönen referansı self dışında bir kaynağa (örneğin yönteme ayrı bir parametre olarak verilen başka bir referansa) bağlandığında ortaya çıkardı; bu, önceki dersin sınırlayıcı ölçümünün burada da geçerli olduğu anlamına geliyor.

YS10. Bir yapı tanımı yaşam süresi parametresi taşıması, o yapı üzerindeki her yöntemin de açık açıklama taşıması gerektiği anlamına gelmiyor. Çıkarım kuralları yapı yöntemlerinde de işliyor; parametre yalnızca yapının tanımında, alanın kendisi için gerekiyor.

Sınırlayıcı Ölçüm: Yapı Tanımı Yaşam Süresi Parametresi Olmadan Yazılamaz

OduncYapi tanımı <'a> parametresi olmadan yazılamıyor. icerik: &Iz biçiminde, hiçbir yaşam süresi belirtmeden yazılan bir alan derleyici tarafından reddediliyor, çünkü yapı tanımı referansın hangi kaynağa bağlı olduğunu ve yapının o kaynaktan daha uzun yaşamayacağını hiçbir yerde söylemiyor. Bu, önceki iki derste görülen kalıbın yapı tanımlarındaki karşılığı: bir referans, ilişkisi kaynağa yazılmadan var olamıyor.

Burada çıkarım kuralları hiç devreye girmiyor — önceki ders görülen üç kural yalnızca işlev imzalarındaki elenmiş referanslar için tanımlanmıştı, yapı alanları için değil. Bir yapının alanı, girdi ya da çıktı değil; yapı her çağrıldığında yeniden değerlendirilen bir imza değil, kendi başına duran bir veri düzeni. Derleyicinin burada varsayabileceği “tek doğru okuma” da yok: bir alan referans taşıyorsa, o referansın kaynağı yapının dışında bir yerde olmak zorunda ve bu kaynağın kim olduğu yalnızca yazan kişi tarafından bilinebilir bir bilgi.

Kabul edilen yazım yukarıdaki OduncYapi<'a> — <'a> parametresi yapının kendisine ekleniyor, alan &'a Iz oluyor ve derleyici bu ilişkiyi doğruluyor: OduncYapi’nin herhangi bir örneği, icerik’in işaret ettiği veriden daha uzun yaşayamaz. Yukarıdaki ölçüm bu sınırın içinde kaldı — yapi her zaman odunc_veri’den önce bitti, hiç sonra değil. yapi örneğinin kendi iç kapsamının dışına taşınmaya çalışıldığını, döndürüldüğünü ya da dış kapsamdaki bir değişkene atandığını düşünelim: bu derlenmezdi, çünkü örneğin taşıdığı referans odunc_veri’den daha uzun yaşayamaz — kısıt, Ödünç Denetleyicisi dersinde koşturulan sınırın yapı alanlarındaki biçimi.

YS11. Bir yapı, referans taşıyan bir alana sahipse, yaşam süresi parametresi olmadan tanımlanamaz. Parametre eklenip ilişki yazılınca yapı kullanılabilir hâle geliyor, ama bu yapının hiçbir örneği kendi referans verdiği veriden uzun yaşayamıyor — parametre bu sınırı kaldırmıyor, yalnızca kaynağa yazıyor.

İki Kurgu, Bir Tasarım Kararı

SahipYapi ile OduncYapi arasındaki seçim bir doğruluk sorunu değil, bir tasarım kararı. Taşınan alan yapıyı veriden bağımsız kılıyor: SahipYapi bir kez kurulduktan sonra, verinin başka bir yerde yaşayıp yaşamadığını umursamadan taşınabiliyor, döndürülebiliyor, uzun süre saklanabiliyor. Bunun bedeli bir kopya ya da bir taşıma — veri artık yalnızca o yapıya ait.

Ödünç alınan alan ise veriyi paylaşmayı mümkün kılıyor: aynı Iz değeri hem kendi bağlamasında kullanılabiliyor hem OduncYapi üzerinden okunabiliyor, üstelik bir kopya çıkarmadan. Bunun bedeli de yapının kendi ömrünün veriye bağlanması — OduncYapi, verisinden daha uzun yaşayamadığı için, dönüş değeri olarak kullanılması ya da uzun süre saklanması genellikle daha kısıtlı. Hangi kurgunun seçileceği, yapının ne kadar süre yaşayacağına ve verinin başka yerlerde de kullanılıp kullanılmayacağına bağlı.

Bu iki seçenek arasında üçüncü bir yol yok — bir alan ya sahip oluyor ya ödünç alıyor, ikisinin ortası bulunmuyor. Bir alanın ödünç aldığı veri üzerinde yazma yetkisi de olabilir (&'a mut Iz), ama bu da hâlâ ödünç alma kategorisinde kalıyor: yapı yine veriye sahip olmuyor, yalnızca üzerinde değiştirme izni taşıyan bir referans tutuyor ve aynı yaşam süresi koşuluna tabi oluyor. Kursun ilerleyen kısmında görülecek paylaşımlı sahiplik araçları (öbekte tekil sahiplik, referans sayımı) bu iki seçeneğin ortasında duran üçüncü bir yol gibi görünebilir, ama aslında değil — onlar da temelde birer sahiplik biçimi, yalnızca sahiplik kuralının nasıl uygulandığı farklılaşıyor. Referans tutmak, verinin hiçbir zaman yapıya geçmediği tek durum olarak kalıyor.

Özet

  • Bir yapının alanı bir referans tutuyorsa, yapı tanımı bir yaşam süresi parametresi taşımak zorundadır; bu parametre olmadan yazılan tanım reddedilir.
  • Bir yapının alanına taşınan bir değer, yapının kendisiyle birlikte biter: ömrü taşındığı yerde bitiyor.
  • Bir yapının alanında tutulan bir referansın verisi, yapının kendisinden sonra biter — yapı asla verisinden uzun yaşayamaz.
  • Yaşam süresi parametresi bu sınırı kaldırmaz, yalnızca kaynağa yazar; parametre eklenince bile bir yapı örneği kendi referans verdiği veriden uzun yaşayamaz.
  • Taşınan alanla ödünç alınan alan arasındaki seçim bir tasarım kararıdır: biri yapıyı veriden bağımsız kılar, öbürü veriyi paylaşmayı mümkün kılar.
  • Yaşam süresi parametresi yalnızca yapının tanımında gereklidir; parametreyi taşıyan bir yapının yöntemleri, çıkarım kuralları uyduğu sürece kendi başına açıklama taşımayabilir.

Sonraki Adım

Bu kursun ilk iki konusu boyunca bir değer bir yapıya ya taşındı ya ödünç verildi ve her iki durumda da o değerin tek bir sahibi vardı — taşınan alanın sahibi yapıydı, ödünç verilen verinin sahibi kendi kapsamıydı; ödünç alınan durumda bile sahiplik hiç bölünmedi, yalnızca geçici olarak paylaşıldı. Sıradaki konu bu varsayımı kaldırıyor: bir değerin sahibi birden çok olabilir mi ve bu durumda “bitiş” ne anlama gelir? Öbekte tekil sahiplikle başlayan bu bölüm, önce özyinelemeli tipleri mümkün kılan tek bir öbek sahipliğini, sonra referans sayımıyla gerçek anlamda paylaşılan sahipliği ö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