İçeriğe geç
academia.sh

Ders 08 / 14

Yaşam Süresi Çıkarım Kuralları

Derleyicinin bir imzada yazılmayan yaşam süresi açıklamasını hangi kurallarla türettiği ölçülüyor. Aynı imza açık ve örtük biçimde yazılıp koşturuluyor ve ikisinin de aynı davranışı verdiği gösteriliyor; çıkarımın kapsamadığı imzada açıklamanın yine zorunlu kaldığı bir redd-kabul çiftiyle sınanıyor.

Yayın Güncelleme

Bu derste

  • Yaşam süresi çıkarımının kapsadığı iki imza biçimini tanımlar
  • Aynı imzanın açık ve örtük biçimlerini koşturarak aynı davranışı verdiğini gösterir
  • Çıkarımın kapsamadığı bir imzada açıklamanın hâlâ zorunlu olduğunu redd-kabul çiftiyle gösterir
İçindekiler

Önceki ders açık yazımın ne zaman gerektiğini ve neyi ifade ettiğini ölçtü. Ama kursun daha önceki derslerinde geçen işlevlerin çoğu — tek girdi alıp bir referans döndüren, ya da self üzerinden çalışan yöntemler — hiç açık açıklama taşımadı ve yine de derlendi. Bu ders o boşluğu dolduruyor: derleyici, yazılmayan açıklamayı hangi kurallarla türetiyor ve bu kuralların kapsamadığı imzalarda ne oluyor.

Bu soru M09/K01’in “yazılmayanın karşılığı ne” eksenine benziyor ama aynı şey değil: orada ölçülen, yazılmayan bir tür bilgisinin derleyici tarafından nereden tamamlandığıydı; burada ölçülen bir referansın ömrünün nerede bittiği — çıkarım kuralları yalnızca bu bilgiyi kaynağa yazma zorunluluğunu kaldırıyor, bilginin kendisini değiştirmiyor. Eşzamanlılık kursunun (M09/K03) sorduğu “koşan bir birim nasıl duracak” sorusu da burada geçerli değil; bu ders hâlâ bir referansın geçerlilik aralığını ölçüyor, bir çalışma biriminin durmasını değil.

Çıkarımın kendisi yeni bir bilgi kaynağı değil. Önceki derste kurulan ilke geçerliliğini koruyor: derleyici hiçbir zaman tahmin yürütmüyor, yalnızca her zaman aynı sonuca varacağı durumlarda yazımı kısaltıyor. Bir imza biçimi çıkarımın kapsamına girdiğinde, o biçimdeki her imza için tek bir doğru okuma var demektir; girdiden çıktıya giden yol birden fazla olasılık taşımıyor. Bu ders o kapsamın nerede başlayıp nerede bittiğini ölçüyor.

Bu, kaynağı okuyan bir kişi açısından da önemli bir sonuç doğuruyor: bir imzada açıklama görünmüyorsa, bu iki farklı şeyi ifade edebilir. Ya işlevin dönen değerle hiçbir referans ilişkisi yok, ya da ilişki var ama imza biçimi çıkarımın kapsadığı iki kalıptan birine giriyor. Bu ikisi arasındaki fark, imzaya bakıp girdi sayısını ve self’in varlığını kontrol ederek ayırt ediliyor — aşağıdaki iki ölçüm bu kontrolü somutlaştırıyor.

Üç Kural, İki Kolaylık

Bir imzada açıklama yazılmadığında derleyicinin varsayacağı ilişkiye çıkarım (elision) deniyor. Rust’ın dil belirtimi bu varsayımı üç kuralla tanımlıyor. İlk kural her girdi referansına kendi ayrı, adsız bir ömür veriyor — bu her zaman geçerli ve kendi başına bir çıktı üretmiyor. İkinci kural, girdi tam olarak bir referansa sahipse, o girdinin ömrünü bütün elenmiş çıktı referanslarına atıyor. Üçüncü kural, girdiler arasında &self ya da &mut self varsa, self’in ömrünü bütün elenmiş çıktı referanslarına atıyor.

İkinci ve üçüncü kural, pratikte açıklamayı gereksiz kılan kurallar — ilk kural yalnızca girdi tarafını adlandırıyor, çıktıyı hiçbir girdiye bağlamıyor. Örneğin iki referans alıp hiç referans döndürmeyen bir işlevde yalnızca ilk kural işliyor: her girdi kendi ayrı ömrünü alıyor ve mesele orada bitiyor, çünkü elenecek bir çıktı yok. İkinci ve üçüncü kural devreye ancak elenmiş bir çıktı referansı olduğunda giriyor ve bu çıktının hangi girdiye bağlanacağını belirliyor.

İkinci kuralın “tam olarak bir girdi” koşulu, girdi sayısına değil girdideki referans sayısına bakıyor. Bir işlev bir sayı ve bir referans birlikte alıyorsa, girdi listesi iki öğe taşısa da ömür taşıyan girdi hâlâ tek — ikinci kural yine uygulanıyor. Aynı biçimde, bir işlev tek bir referans alıp birden çok referans içeren bir demet döndürürse, elenmiş her çıktı parçası da aynı tek girdinin ömrünü alıyor; kural “tek bir çıktıya” değil, “elenmiş bütün çıktılara” uygulanıyor. Aşağıdaki iki ölçüm bu iki kolaylığı ayrı ayrı gösteriyor: önce tek girdili bir işlev, sonra &self alan bir yöntem.

Ölçüm: Tek Girdili İmza

Bir işlev yalnızca bir referans alıp bir referans döndürüyorsa, ikinci kural devreye giriyor: elenmiş tek girdinin ömrü, elenmiş çıktıya atanıyor. Aşağıda aynı işlev iki kez yazılıyor — biri açıklamasız, biri açık 'a ile — ve ikisi de aynı sonucu veriyor.

// cikarim_tek_girdi.rs — tek girdili imza: aciklama yazilmasa da (elision) ayni davranis.

struct Iz {
    ad: String,
}

// Ortuk: derleyici tek girdinin omrunu cikan referansa atiyor.
fn ilk_uc_harf(iz: &Iz) -> &str {
    &iz.ad[..3]
}

// Acik: ayni iliski elle yazildi.
fn ilk_uc_harf_acik<'a>(iz: &'a Iz) -> &'a str {
    &iz.ad[..3]
}

fn main() {
    let deger = Iz { ad: "kapsam-1".to_string() };
    println!("ortuk -> {}", ilk_uc_harf(&deger));
    println!("acik  -> {}", ilk_uc_harf_acik(&deger));
}
ortuk -> kap
acik  -> kap

ilk_uc_harf ve ilk_uc_harf_acik aynı gövdeyi taşıyor ve aynı çıktıyı veriyor; aralarındaki tek fark kaynaktaki açıklamanın görünürlüğü. Derleyici, birincisini derlerken ikincisiyle aynı ilişkiyi kendisi kuruyor — girdi tek olduğu için başka bir ihtimal de yok: dönen referans ya iz’den geliyor ya da hiç yok.

YS6. Çıkarım kuralları, derleyicinin her zaman aynı sonuca varacağı durumlarda açıklamayı gereksiz kılıyor. Tek girdili bir imzada dönen referansın hangi girdiye bağlı olacağı konusunda gerçek bir belirsizlik yok — girdi zaten bir tane.

Bu, önceki dersteki oncelikli_ad işleviyle karşılaştırıldığında daha net görülüyor: oncelikli_ad iki girdi aldığı için açıklamasız yazılamazdı, çünkü dönen referansın oncelikli’den mi digeri’den mi geldiği kaynaktan çıkarılamazdı. ilk_uc_harf tek girdi aldığı için aynı belirsizlik hiç oluşmuyor — çıkarım kuralı burada gerçek bir seçim yapmıyor, yalnızca zaten tek olan seçeneği kaynağa yazmaktan kurtarıyor.

Ölçüm: self Alan Yöntem

Üçüncü kural, bir yöntem self’i ve başka referanslar alsa bile, elenmiş çıktıyı hep self’in ömrüne bağlıyor. Bu, kursun önceki derslerinde görülen birçok yöntemin neden hiç açıklama taşımadığını açıklıyor.

// cikarim_self.rs — &self alan yontem: cikan referans self'e baglaniyor, aciklama yazilmasa da.

struct Iz {
    ad: String,
}

impl Iz {
    // Ortuk: self'in omru cikan referansa atanir.
    fn adi(&self) -> &str {
        &self.ad
    }
}

struct IzAcik {
    ad: String,
}

impl IzAcik {
    // Acik: ayni atama elle yazildi.
    fn adi<'a>(&'a self) -> &'a str {
        &self.ad
    }
}

fn main() {
    let deger = Iz { ad: "kapsam-2".to_string() };
    let acik = IzAcik { ad: "kapsam-2".to_string() };
    println!("ortuk -> {}", deger.adi());
    println!("acik  -> {}", acik.adi());
}
ortuk -> kapsam-2
acik  -> kapsam-2

Iz::adi ile IzAcik::adi aynı davranışı veriyor. Burada dikkat çeken şey, yöntemin self dışında başka bir referans alsaydı bile sonucun değişmeyeceği — üçüncü kural yalnızca self’e bakıyor, diğer girdileri hiç hesaba katmıyor. Bu, ikinci kuraldan farklı bir kaynağa dayanan aynı sonuç: derleyici, self alan bir yöntemde dönen referansın neredeyse her zaman self’in bir parçası olduğunu varsayıyor ve bu varsayım yeterince güvenilir kabul edildiği için kurala dönüştürülmüş.

Bu iki kural bir arada, kursun önceki derslerinde görülen yöntemlerin çoğunun neden hiç açıklama taşımadığını da açıklıyor: bir tipin kendi alanına referans döndüren yöntemleri &self alıyordu ve üçüncü kural devreye giriyordu; bir değeri okuyup türetilmiş bir referans döndüren serbest işlevler çoğunlukla tek girdiliydi ve ikinci kural devreye giriyordu. Açık açıklamanın gerçekten göründüğü yer, önceki derste olduğu gibi, birden fazla girdinin self olmadan bir arada bulunduğu imzalardı.

YS7. İki farklı kural, iki farklı imza biçiminde aynı sonucu üretiyor: tek girdili işlevlerde girdinin kendisi, self alan yöntemlerde self’in kendisi, çıktının ömrünü belirliyor. Bu iki biçimde açıklama hiç yazılmasa da imza kabul ediliyor; ikisinin de ortak noktası, dönen referansın gidebileceği tek bir yerin olması.

Bir İmzayı Okumanın Sırası

Yukarıdaki iki ölçüm birlikte, bir imzaya bakıp açıklamanın gerekip gerekmediğini belirlemenin sırasını da veriyor. Önce girdilerdeki referans sayısı sayılıyor: hiç referans yoksa ya da elenecek bir çıktı referansı yoksa, ilk kural tek başına yetiyor ve mesele kapanıyor. Girdide tam olarak bir referans varsa, ikinci kural devreye giriyor ve çıktı ona bağlanıyor — ilk_uc_harf bu yola giriyor. Girdide birden fazla referans varsa, sıradaki soru self’in olup olmadığı: varsa üçüncü kural devreye giriyor ve çıktı self’e bağlanıyor — Iz::adi bu yola giriyor. Birden fazla referans var ve self yoksa, üç kuraldan hiçbiri tek bir sonuca varamıyor ve açık açıklama zorunlu hâle geliyor — daha_uzun_olan bu son duruma düşüyor.

Bu sıralama, derleyicinin kendi iç işleyişinden çok, bir okuyucunun bir imzaya bakıp açıklama bekleyip beklemeyeceğini hızlıca kestirmesi için kullanışlı bir kısayol. Kurs boyunca görülen işlevlerin çoğu ilk iki basamakta çözülüyor; açık açıklamanın göründüğü yerler, tam da bu sıralamanın son basamağına düşen, birden fazla eşit ağırlıklı referans taşıyan imzalar oluyor.

Bu sıralamanın kendisi bir araç ya da bir doğrulayıcı gerektirmiyor — üç madde ezberlenip herhangi bir imzaya elle uygulanabiliyor. Bu da yaşam süresiyle ilgili önceki ölçümlerle aynı örüntüyü tekrarlıyor: kaynak, kendi başına, çalıştırılmadan okunabilir bir bilgi taşıyor. Farkı, burada okunanın bir referansın aralığı değil, o aralığın yazılıp yazılmadığı kararı olması.

Sınırlayıcı Ölçüm: Çıkarım Kolaylıktır, Kural Değildir

Çıkarım kuralları üç maddeyle sınırlı ve bu üç maddenin kapsamadığı imzalar var. En açık örnek, self içermeyen ve birden fazla referans alan bir işlev: ne ikinci kural (tek girdi yok), ne üçüncü kural (self yok) devreye giriyor. Böyle bir işlevde dönen referansın hangi girdiye bağlı olduğu konusunda derleyicinin kendiliğinden varacağı tek bir sonuç yok — birinci kural her girdiye kendi ayrı ömrünü veriyor ve hiçbirini çıktıya atamıyor.

Önceki derste görülen iki referanslı, self’siz işlev (oncelikli_ad) tam olarak bu durumdaydı ve açık 'a taşıyordu — o derste açıklama, iki girdiden yalnızca birine bağlanan dar bir söz kuruyordu. Aynı sınır burada yeni bir örnekle, bu kez her iki girdinin de gerçekten dönebildiği bir durumla sınanıyor: iki Iz referansı alıp adı daha uzun olanın dilimini döndüren bir işlev, açıklamasız yazılırsa derleyici hangi girdinin döndürüldüğünü belirleyemediği için reddediyor. Gövde, çalışma zamanındaki bir karşılaştırmaya göre x’i de y’yi de döndürebiliyor — bu da girdilerin eşit ağırlıklı olduğunu, birinin diğerine göre öncelikli olmadığını gösteriyor. Kabul edilen yazım ise açık 'a ile ikisini de aynı ömre bağlıyor.

// cikarim_disi.rs — cikarimin kapsamadigi imza: aciklama yazilmadan derlenmiyor, yazilinca derleniyor.

struct Iz {
    ad: String,
}

fn daha_uzun_olan<'a>(x: &'a Iz, y: &'a Iz) -> &'a str {
    if x.ad.len() >= y.ad.len() { &x.ad } else { &y.ad }
}

fn main() {
    let a = Iz { ad: "kisa".to_string() };
    let b = Iz { ad: "daha-uzun-ad".to_string() };
    println!("{}", daha_uzun_olan(&a, &b));
}
daha-uzun-ad

daha_uzun_olan’ın açıklamasız hâli üç kuraldan hiçbirine uymuyor: iki girdi var, self yok. Derleyici, dönen referansın x’ten mi y’den mi geldiğini belirleyemediği için imzayı reddediyor; kabul edilen yazım, önceki dersteki digerinin_adi işlevinde görülen çözümün aynısını uyguluyor — iki girdiyi de aynı 'a’ya bağlamak. Yukarıdaki koşum bu kabul edilen biçimle çalışıyor ve iki adı karşılaştırıp uzun olanı döndürüyor.

digerinin_adi ile daha_uzun_olan arasındaki fark, kabul edilme gerekçesinde değil, dönüş kararının ne zaman belli olduğunda: digerinin_adi’de hangi parametrenin döndürüleceği işlevin gövdesinde sabitti, daha_uzun_olan’da bu karar her çağrıda yeniden, çalışma zamanındaki bir karşılaştırmayla veriliyor. İki durumda da açıklama aynı işi görüyor — dönebilecek her kaynağı önceden kapsıyor — çünkü derleme zamanında hangi dalın seçileceği bilinmiyor; açıklama olası bütün yolları kapsamak zorunda, yalnızca gerçekte izlenen yolu değil.

YS8. Çıkarım kuralları bir kolaylıktır, dilin her imzayı derleyebileceği anlamına gelmez. Üç kuraldan hiçbiri bir imzaya uymadığında açıklama yine zorunlu; kural yalnızca ne zaman gerekmediğini belirliyor, her zaman gerekmeyeceğini değil.

Bu sınır, önceki derste kurulan ilkeyle aynı köke dayanıyor: derleyici bir açıklamayı yalnızca kaynaktan tek bir doğru okuma çıkıyorsa atlıyor. daha_uzun_olan gibi iki eşit ağırlıklı girdisi olan bir işlevde bu tek okuma yok — x ve y aynı tipte, aynı konumda, ikisi de dönebilir. Üç kural da bu belirsizliği çözecek bir varsayım içermiyor, çünkü herhangi bir varsayım bazı doğru programları reddedip bazı yanlış olanları kabul edebilirdi. Rust bu riski almak yerine yazımı reddediyor ve kararı geliştiriciye bırakıyor; bu da açıklamanın kolaylık değil güvence olduğu yerde neden hâlâ zorunlu kaldığını gösteriyor.

Özet

  • Rust’ın çıkarım kuralları, yaşam süresi açıklamasının bazı imza biçimlerinde yazılmasını gereksiz kılıyor; bunlardan ikisi kursta ölçüldü: tek girdili işlevler ve self alan yöntemler.
  • Tek girdili bir işlevde elenmiş girdinin ömrü elenmiş çıktıya atanıyor; self alan bir yöntemde bu atama girdi sayısından bağımsız olarak hep self’e yapılıyor.
  • Aynı imzanın açık ve örtük biçimleri, ölçülen iki durumda da birebir aynı davranışı verdi; iki biçimde de değerler kapsam sonunda bitti.
  • Bu ders M09/K01’in yazılmayan tür bilgisini ölçen ekseninden farklı: burada ölçülen bir referansın ömrünün nerede bittiği, tür bilgisinin nereden tamamlandığı değil.
  • Üç kuralın kapsamadığı imzalarda (birden çok girdi, self yok) açıklama zorunlu kalıyor; kapsamayan bir imza açıklamasız yazılırsa reddediliyor.
  • Çıkarım, derleyicinin bir imzadan tek bir doğru okuma çıkardığı durumlarda geçerli; birden fazla eşit ağırlıklı olasılık varsa derleyici bir varsayımda bulunmak yerine yazımı reddediyor.

Sonraki Adım

Bu derste çıkarımın hangi imzaları kapsadığı ve hangilerini kapsamadığı görüldü — kapsayan imzalarda açıklama gereksizdi, kapsamayanlarda hâlâ zorunluydu ve bu zorunluluk işlev parametreleri arasında kuruldu. Ama aynı ihtiyaç bir yapının kendi alanında da ortaya çıkabilir: bir yapı bir referans tutuyorsa, o yapının kendisi referans verdiği veriden daha uzun yaşayamaz ve bu koşulun da bir açıklamayla yazılması gerekir. Sıradaki ders bu koşulu, aynı veriyi bir yapıya taşıyarak ve ödünç vererek tutan iki kurguyu karşılaştırarak ö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