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.