Ders 07 / 14
Açık Yaşam Süresi Açıklamaları
Bir işlev birden çok referans alıp içlerinden birini döndürdüğünde, dönen referansın hangi girdiye bağlı olduğu kaynaktan tek başına çıkarılamaz ve açık bir yaşam süresi açıklaması gerekir. Açıklamanın bir referansın ömrünü uzatmadığı ya da kısaltmadığı, yalnızca var olan bir ilişkiyi kaynağa yazdığı ve derleyicinin bu yazıyı bir istek olarak doğruladığı ölçülüyor.
Yayın Güncelleme
Bu derste
- Bir işlev imzasında açık yaşam süresi açıklamasının ne zaman gerekli olduğunu belirler
- Açıklamanın dönen referansı hangi girdiye bağladığını koşturarak gösterir
- Karşılanamayan bir açıklamanın reddedildiğini, karşılanabilir hâle getirilince kabul edildiğini gösterir
İçindekiler
Önceki ders iki bağımsız referansın aralığının hiçbir ilişkiye girmeden yan yana durabileceğini gösterdi — çoğu referans birbirinden habersiz yaşar ve derleyici aralarında hiçbir bağ kurmaz. Ama bir işlev birden çok referans alıp içlerinden birini döndürdüğünde, artık bağımsızlık yetmiyor: dönen referansın hangi girdiden geldiği, hangi girdinin ömrüne bağlı olduğu kaynaktan tek başına çıkarılamıyor. Bu ders tam bu noktada devreye giren açık yazımı — yaşam süresi açıklamasını — ölçüyor: ne zaman gerekiyor, hangi bilgiyi ekliyor ve derleyici onu nasıl bir söz olarak ele alıyor.
Eşzamanlılık kursunun (M09/K03) ölçtüğü şey burada da geçerli değil: orada koşan bir birimin durması dışarıdan bir kararla tetikleniyordu, burada ölçülen bir kaynağın kendi imzasına yazılmış bir ilişki. Yığıt ve Öbek dersinde kurulan dört bitiş sınıfından hiçbiri de burada işlemiyor; açıklama bir değerin bitişini değil, iki referans arasındaki bir ilişkiyi yazıya döküyor.
Bu ilişkinin yazılması ihtiyaridir değil zorunludur: derleyici, belirsiz kalan bir imzayı hiçbir varsayımda bulunmadan reddediyor. Bir insan okuyucu iki referanslı bir işleve bakıp “muhtemelen ilk parametre döndürülüyor” diye tahmin edebilir, ama derleyici tahmin yürütmüyor — ya ilişki açıkça yazılı olacak ya da işlev hiç derlenmeyecek. Bu, kursun daha önce kurduğu bir ilkeyi tekrarlıyor: derleyicinin güvencesi, olası bir doğru okumaya değil, kaynakta yazılı olan bilgiye dayanıyor.
Ne Zaman Yazılması Gerekiyor
Bir işlev tek bir referans alıp bir referans döndürüyorsa, dönen referansın hangi girdiye
bağlı olduğu açık: alınan tek girdi. Ama iki referans alıp bir referans döndüren bir işlevde
bu bağ belirsizleşiyor — derleyici, dönen referansın ilk girdiye mi, ikinci girdiye mi, yoksa
ikisine birden mi bağlı olduğunu kaynaktaki tipe bakarak tek başına söyleyemez. Tam da bu
belirsizlik anında yaşam süresi açıklaması gerekiyor: işlev imzasına yazılan kısa bir ad
('a gibi), hangi girdinin ömrünün dönen referansa taşındığını söylüyor.
Bu belirsizlik yalnızca girdi sayısıyla ilgili değil, dönen değerin bir referans olmasıyla da
ilgili. Bir işlev iki referans alıp bir sayı ya da yeni kurulmuş bir değer döndürüyorsa hiçbir
açıklamaya gerek yok, çünkü dönen değerin ömrü herhangi bir girdiye bağlı değil — kendi başına
var oluyor. Açıklama yalnızca dönen değer de bir referans olduğunda ve o referansın hangi
girdiden geldiği tipten okunamadığında devreye giriyor. Aşağıdaki ölçüm tam bu durumu kuruyor:
iki Iz referansı alan, ama yalnızca birinden türeyen bir dize dilimi döndüren bir işlev.
// iki_kaynakli_donus.rs — donen referans yalnizca 'a'yi tasiyan girdiye bagli.
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()
}
// 'a yalnizca oncelikli ve donen deger arasinda kuruluyor; digeri bu iliskiye girmiyor.
fn oncelikli_ad<'a>(oncelikli: &'a Iz, digeri: &Iz) -> &'a str {
println!("digeri de gorunuyor -> {}", digeri.ad);
&oncelikli.ad
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
let disarida = Iz::yeni("disarida-yasayan", &defter);
let sonuc;
{
let iceride = Iz::yeni("iceride-yasayan", &defter);
sonuc = oncelikli_ad(&disarida, &iceride);
println!("cagri icinde sonuc -> {}", sonuc);
}
println!("ic kapsam bitince defter -> [{}]", okunan(&defter).join(", "));
println!("sonuc hala kullanilabilir -> {}", sonuc);
}
digeri de gorunuyor -> iceride-yasayan cagri icinde sonuc -> disarida-yasayan ic kapsam bitince defter -> [iceride-yasayan] sonuc hala kullanilabilir -> disarida-yasayan
oncelikli_ad iki referans alıyor ama açıklama yalnızca oncelikli ile dönen değer arasında
kuruluyor: her ikisi de aynı 'a’yı taşıyor, digeri taşımıyor. Bu, iki kaynağın ömrünün
ayrıştığı bir durum — disarida dış kapsamda, iceride iç kapsamda yaşıyor ve ikisinin
ömrü farklı uzunlukta. Ölçüm bunu doğruluyor: iç kapsam kapanıp iceride-yasayan deftere
düştükten sonra bile sonuc hâlâ kullanılabiliyor, çünkü sonuc yalnızca disarida’nın
ömrüne bağlı — digeri’nin (yani iceride’nin) ömrüne hiç bağlı değil.
YS4. Açık yaşam süresi açıklaması, iki referans arasında var olan bir ilişkiyi
kaynağa yazıyor; ilişkiyi kurmuyor, kurulu olanı görünür kılıyor. oncelikli_ad işlevinde bu
ilişki yalnızca oncelikli ile dönen değer arasında; digeri işlevin gövdesinde kullanılsa
bile açıklamanın dışında kalıyor.
Ölçümde digeri (iceride’ye bağlı) kaybolduktan sonra sonuc’un hâlâ kullanılabilmesi
tesadüf değil, imzanın doğrudan söylediği bir sonuç. disarida, iceride’den önce kurulmuş
ve iç kapsam kapanana kadar hâlâ ayakta; sonuc da disarida’nın ömrüne bağlı olduğu için,
iceride bitse bile geçerliliğini koruyor. Eğer işlev digeri’yi döndürseydi, aynı çağrı
iceride’nin ömrü bitince artık kullanılamayan bir referans üretirdi — bu da imzanın neden
oncelikli’yi seçtiğini, digeri’yi değil, somutlaştırıyor.
Burada dikkat edilmesi gereken bir nokta daha var: 'a yazımı, disarida’nın ömrünü ne
uzatıyor ne kısaltıyor. disarida zaten dış kapsamda, iceride’den önce kurulmuş ve dış
kapsamın sonuna kadar yaşayacaktı — açıklama olmasaydı da bu değişmezdi. Açıklamanın tek işi,
dönen referansın hangi ömre bağlı olduğunu derleyiciye ve okuyan kişiye söylemek; kendisi
hiçbir ömrü değiştirmiyor.
Açıklamanın Kapsadığı ve Kapsamadığı
oncelikli_ad imzasında 'a yalnızca oncelikli ve dönen değer arasında geçiyor; digeri
parametresi kendi ayrı, adlandırılmamış bir ömre sahip ve bu ömrün 'a ile hiçbir ilişkisi
yok. Bu, iki referansın aynı işlev çağrısında bir arada bulunmasının, aralarında bir ilişki
kurulduğu anlamına gelmediğini gösteriyor — ilişki yalnızca imzada yazılan kadar var.
digeri’nin ömrü oncelikli’ninkinden kısa da olabilir, ondan uzun da; işlev bunu hiç
umursamıyor, çünkü gövde digeri’yi yalnızca okumak için kullanıyor, döndürmek için değil.
Bu, açıklamanın imzada olabildiğince az şey söylemesinin bir erdem olduğunu gösteriyor.
digeri parametresine bir ad vermek zorunda değildik — kendi ömrü hiçbir yerde
kullanılmayacaksa, derleyici onu kendiliğinden ayrı ve adsız bir aralık olarak ele alıyor.
Yalnızca gerçekten paylaşılan bir ilişki varsa bir ad gerekiyor; ilişki yoksa ad da yok.
Bu ayrım, önceki dersteki sınırlayıcı ölçümle aynı köke dayanıyor: bir işlev imzası iki
referans arasında bir bağ kurmadığı sürece, o iki referansın aralığı derleyici için
bağımsız kalıyor. 'a burada yalnızca oncelikli ile dönen değeri birbirine bağladı;
digeri’yi bu bağın dışında bıraktı ve digeri kendi bağımsız aralığında kaldı.
Bir imza, tek bir 'a yerine birden çok adlandırılmış ömür de taşıyabilir — her biri kendi
ilişkisini kurar. Aşağıdaki işlev iki referans alıp ikisini de bir demet içinde döndürüyor;
'a yalnızca ilk parçayı, 'b yalnızca ikinci parçayı bağlıyor ve ikisi arasında hiçbir
ilişki kurulmuyor.
// iki_isimli_omur.rs — iki ayri isimlendirilmis omur, ikisi de donen demetin ayri bir parcasina bagli.
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()
}
// 'a ve 'b birbirinden bagimsiz: hicbiri otekini kisitlamiyor.
fn ikisini_de_dondur<'a, 'b>(x: &'a Iz, y: &'b Iz) -> (&'a str, &'b str) {
(&x.ad, &y.ad)
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
let disarida = Iz::yeni("uzun-omurlu", &defter);
let ilk;
{
let iceride = Iz::yeni("kisa-omurlu", &defter);
let (a, b) = ikisini_de_dondur(&disarida, &iceride);
ilk = a;
println!("ic kapsam icinde -> {}, {}", ilk, b);
}
println!("ic kapsam bitince defter -> [{}]", okunan(&defter).join(", "));
println!("ilk hala kullanilabilir -> {}", ilk);
}
ic kapsam icinde -> uzun-omurlu, kisa-omurlu ic kapsam bitince defter -> [kisa-omurlu] ilk hala kullanilabilir -> uzun-omurlu
Demetin ikinci parçası (b, 'b’ye bağlı) yalnızca iç kapsamın içinde kullanılıyor ve o
kapsamla birlikte konu dışı kalıyor — iceride deftere düşüyor. Demetin ilk parçası (ilk,
'a’ya bağlı) ise dış kapsamda saklanıyor ve iceride bittikten sonra da kullanılabiliyor.
İki isimlendirilmiş ömür burada gerçekten farklı uzunlukta ömürlere karşılık geldi ve derleyici
bunu kabul etti, çünkü imza ikisi arasında hiçbir eşitlik istemedi. 'a ve oncelikli_ad
imzasındaki tek 'a, aynı harfi taşısalar da farklı işlevlerde farklı somut aralıklara denk
gelir — ad, imzanın kendi kapsamında geçerli bir etikettir, işlevler arasında paylaşılan bir
kayıt değildir.
Sınırlayıcı Ölçüm: Açıklama Bir Söz Değil, Bir İstektir
Açıklama yazmak, derleyiciye bir bilgi vermek gibi görünse de aslında bir istek: “dönen
referans en az 'a kadar yaşasın” demenin biçimi. Derleyici bu isteği doğrulamak zorunda ve
istek gövdedeki koşumla tutmuyorsa yazım reddediliyor. Bu, açıklamayı bir belge notundan
ayıran şey: bir yorum satırı yanlış da olsa derlemeyi etkilemez, ama bir yaşam süresi
açıklaması yanlışsa derleme hiç gerçekleşmez.
oncelikli_ad işlevinin gövdesi digeri.ad’ı döndürmeye çalışsaydı — imza aynı kalıp yalnızca
son satır değiştirilseydi — derleyici bunu kabul etmezdi. İmza 'a’yı yalnızca oncelikli
parametresine bağlıyor; digeri’nin kendi ömrü 'a ile ilişkilendirilmemiş, yani derleyici
açısından digeri’nin çağrıdan sonra ne kadar yaşayacağı bilinmiyor. Dönen değerin 'a kadar
yaşayacağı sözü verilmişken gövde bu sözü karşılamayan bir kaynaktan (digeri) değer
döndürürse, söz tutulamaz hâle gelir ve derleyici imzayı reddeder; reddin gerekçesi,
digeri için de 'a açıklamasının eksik bırakılmış olmasıdır.
Burada derleyicinin elinde başka bir seçenek yok: oncelikli’nin çağrı sırasında ne kadar
yaşayacağı imzadan biliniyor, ama digeri’nin bilinmiyor. Derleyici, çağıranın digeri için
her zaman en az 'a kadar yaşayan bir referans vereceğinden emin olamadığı sürece, gövdenin
digeri’den bir değer döndürmesine izin veremez — aksi hâlde bir çağıran, kısa ömürlü bir
digeri göndererek dönen referansın sahibinden uzun yaşamasına yol açabilirdi. Bu, önceki
derste kurulan sınırın (bir referans sahibinden uzun yaşayamaz) işlev sınırları arasında da
geçerli olduğunu gösteriyor.
Bu isteği karşılanabilir hâle getirmenin iki yolu var. Sahiplik ve Taşıma dersinde görülen
yaklaşım burada da geçerli olurdu: işlev referans yerine sahip olunan bir değer döndürseydi
(digeri.ad.clone() gibi), yaşam süresi sorunu hiç ortaya çıkmazdı, çünkü dönen değerin artık
hiçbir girdiye bağlı bir ömrü kalmazdı. Bu ders o yolu değil, referansı koruyan ikinci yolu
izliyor: iki parametreyi de aynı 'a’ya bağlamak; böylece hem oncelikli hem digeri en az
'a kadar yaşayacağına söz veriyor ve gövde ikisinden herhangi birini döndürebiliyor.
// birlesik_omur.rs — istek karsilanabilir hale getirilince kabul ediliyor: iki parametre de 'a'yi tasiyor.
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());
}
}
// Simdi ikisi de 'a: donen deger her iki parametreden de gelebilir.
fn digerinin_adi<'a>(_oncelikli: &'a Iz, digeri: &'a Iz) -> &'a str {
&digeri.ad
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
let a = Iz::yeni("a", &defter);
let b = Iz::yeni("b", &defter);
println!("{}", digerinin_adi(&a, &b));
}
b
digerinin_adi artık digeri.ad’ı döndürebiliyor, çünkü imza artık iki parametrenin de en
az 'a kadar yaşayacağını söylüyor ve gövde bu sözü karşılıyor — hangi parametre
döndürülürse döndürülsün, söz tutuluyor. oncelikli_ad ile digerinin_adi arasındaki fark,
davranışta değil imzanın kapsadığı sözde: ilki yalnızca bir parametre için söz veriyordu,
ikincisi ikisi için de veriyor.
YS5. Bir yaşam süresi açıklaması, derleyicinin doğrulamak zorunda olduğu bir istektir. Gövde, açıklamanın verdiği sözü tutmayan bir kaynaktan değer döndürürse yazım reddedilir; söz genişletilip her iki kaynağı da kapsayacak hâle getirilince aynı gövde kabul edilir.
digerinin_adi’nin iki parametreyi de aynı 'a’ya bağlaması bedelsiz değil. oncelikli_ad
çağrıldığında digeri’nin oncelikli’den daha kısa yaşaması hiç sorun değildi, çünkü açıklama
digeri’yi hiç kapsamıyordu. digerinin_adi çağrıldığında ise artık ikisi de en az 'a kadar
yaşamak zorunda; çağıran taraf, ömrü daha kısa olan bir referansı digerinin_adi’ye veremez,
çünkü imza bunu izin vermiyor. Sözü genişletmek işlevi daha esnek hale getirmiyor, tam tersine
çağıranın uyması gereken koşulu sıkılaştırıyor. Bu, açıklamanın yalnızca “hangi bilgi
eklendiğini” değil, “eklenen bilginin çağırana ne yüklediğini” de belirlediğini gösteriyor —
en dar açıklama, gövdenin gerçekte ihtiyaç duyduğu kadarı, genellikle tercih edilen olan.
Bu üç ölçüm birlikte açıklamanın üç yüzünü gösterdi: oncelikli_ad bir ilişkiyi yazdı,
ikisini_de_dondur iki ilişkinin bağımsız kalabileceğini gösterdi, digerinin_adi ise
oncelikli_ad’ın karşılayamadığı ilişkinin, açıklama genişletilince kabul edildiğini
gösterdi.
Üçünde de açıklamanın kendisi hiçbir referansın ömrünü değiştirmedi; yalnızca kaynakta zaten
var olan ya da olması istenen bir ilişkiyi yazılı hâle getirdi ve derleyici bu yazının
tutarlılığını sınadı.
Özet
- Açık yaşam süresi açıklaması, bir işlev birden çok referans alıp içlerinden birini döndürdüğünde ve dönen referansın hangi girdiye bağlı olduğu kaynaktan çıkarılamadığında gerekir.
- Açıklama, referanslar arasında var olan bir ilişkiyi kaynağa yazar; herhangi bir referansın ömrünü uzatmaz ya da kısaltmaz; referans verilen değerler yine kendi kapsam sonlarında bitiyor.
- Bir imza birden çok adlandırılmış ömür taşıyabilir; her ad yalnızca imzanın kendi kapsamında geçerlidir ve farklı işlevlerdeki aynı ad birbirine bağlı değildir.
- Açıklama, derleyicinin doğrulamak zorunda olduğu bir istektir: dönen değer, açıklamanın söz verdiği kaynaktan gelmelidir.
- Sözü karşılamayan bir gövde reddedilir; söz genişletilip ilgili tüm kaynakları kapsayacak hâle getirilince aynı gövde kabul edilir, ama bu genişletme çağırana yüklenen koşulu da sıkılaştırır.
Sonraki Adım
Bu derste açıklamanın ne zaman yazılması gerektiği, neyi ifade ettiği ve karşılanmadığında
neden reddedildiği görüldü. Ama kursun önceki derslerindeki çoğu işlev — tek girdili, ya da
self alan yöntemler — hiç açık açıklama taşımıyordu ve yine de derlendi; bu işlevlerde
açıklama yazılmadığı hâlde bir ilişki zaten kurulmuş oluyordu. Sıradaki ders bu boşluğu
dolduruyor: derleyicinin yazılmayan açıklamayı hangi kurallarla türettiğini, bu kuralların
kaç imza biçimini kapsadığını ve hangi imzaların bu kuralların dışında kaldığını ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.