İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat