Ders 06 / 14
Yaşam Süresi Kavramı
Bir referansın geçerli olduğu kaynak aralığının bir çalışma zamanı nesnesi olmadığı, yalnız derleyicinin sınadığı bir aralık olduğu kuruluyor. Ödünç Denetleyicisi dersinde sınandığı gösterilen aralığa burada bir ad veriliyor ve iki bağımsız referansın aralığının iç içe olmak zorunda olmadığı ölçülüyor.
Yayın Güncelleme
Bu derste
- Yaşam süresini bir referansın geçerli olduğu kaynak aralığı olarak tanımlar
- Ölçüm çekirdeğinin bir kesitini koşturarak bir referansın kullanım aralığının sahibinin ömrü içinde kaldığını gösterir
- İki bağımsız referansın geçerlilik aralığının iç içe olmak zorunda olmadığını koşturarak gösterir
İçindekiler
Ödünç Denetleyicisi dersi bir şeyi sınadığını gösterdi ama o şeyin adını hiç koymadı: denetleyici, bir referansın ne kadar süre kullanılabileceğine dair bir aralığı kontrol ediyordu. Bu ders tam olarak o aralığa bir ad veriyor ve kursun sorusunu (“bu değerin ömrü nerede bitiyor”) bir referansa uyguluyor: bir referansın kendi geçerlilik aralığı nereden nereye uzanıyor ve bu aralık nereden okunuyor.
Eşzamanlılık kursunun (M09/K03) sorduğu soru farklıydı — orada koşan bir birimin ne zaman duracağı ölçülüyordu, dışarıdan bir kararla (kanal kapanması, bağlam iptali) tetiklenen bir durma. Burada ölçülen bir çalışma birimi değil; bir referansın kaynakta hangi aralıkta kullanılabildiği. Ödünç Alma dersi bu aralığın sonucunu zaten göstermişti: bir referans kapsamı biterken kendisi bir şey bitirmiyordu, ödünç verdiği değer hâlâ yaşıyordu. Bu ders o sonucun nedenine iniyor — referansın kendisine ait, adı konmuş bir aralık var mı, varsa bu aralık nasıl belirleniyor.
Bir Aralığın Adı
Rust’ta yaşam süresi (lifetime), bir referansın geçerli kabul edildiği kaynak aralığının adıdır. Bu aralığın iki ucu var: referansın oluşturulduğu nokta ve son kullanıldığı nokta. Yaşam süresi bu iki nokta arasındaki mesafeyi tanımlıyor ve derleyici, bir referansın bu aralığın dışında kullanılmadığından emin oluyor. Bu denetim olmadığında bir referans, verdiği değer bittikten sonra da elde tutulabilir ve üzerinden okuma yapılmaya çalışılabilirdi; sonuç, artık geçerli olmayan bir belleğe bakan, tanımsız bir okuma olurdu. Rust bu durumu çalışma zamanına hiç bırakmıyor: aralık ihlali, program bir kez bile koşmadan, derleme sırasında yakalanıyor.
Burada kurulması gereken tek ve en önemli ayrım şu: yaşam süresi bir çalışma zamanı
nesnesi değildir. Programın koşumu sırasında hiçbir yerde “bu referansın yaşam süresi” diye
saklanan bir sayaç, bir zaman damgası ya da bir kayıt yok. Önceki derste ölçülen Iz tipi
Drop trait’ini uyguluyordu ve bir değer bittiğinde bunu deftere yazıyordu — ama bu, sahip
olunan değerin bitişiydi. Bir referans (&Iz) Drop uygulamıyor; referansın kendisi hiçbir
zaman deftere düşmüyor, çünkü onun “bitişi” çalışma zamanında gerçekleşen bir olay değil,
derleme zamanında doğrulanan bir sınırdır. Yaşam süresi, kaynaktaki blok yapısından
okunuyor; koşum bittiğinde geriye ona ait hiçbir iz kalmıyor.
YS1. Yaşam süresi bir değer değil, derleyicinin kaynak üzerinde yürüttüğü bir çıkarımın adıdır — programın kendisi çalışırken hiçbir yerde saklanmıyor. Bu, kursun önceki derslerinde ölçülen dört bitiş sınıfından hiçbirine girmiyor: yaşam süresi bir değerin bitişi değil, bir referansın geçerli sayıldığı aralık; ölçülen şey bir olay değil, bir kaynak aralığı.
Bu sınır, bir kez kurulduktan sonra kursun geri kalanında tekrar tekrar kullanılacak: her yeni düzenek (açık açıklama, çıkarım kuralı, yapı alanı, öbekte tekil sahiplik, paylaşımlı sahiplik) aynı soruya farklı bir açıdan yaklaşacak, ama hepsi aynı temele — bir referansın sahibinden uzun yaşayamayacağı gerçeğine — dayanacak.
Rust’ın kendisi, gerektiğinde bir yaşam süresini kesikli çizgiyle başlayan kısa bir adla
('a, 'b) yazıya döker; bu adlandırma bir değer değil, yalnızca derleyiciye ve okuyan kişiye
“şu referansla şu referans aynı aralığa bağlı” demenin bir yolu. Bu adların ne zaman
yazılmasının gerektiği ve neyi ifade ettiği sıradaki dersin konusu; burada önemli olan
yalnızca şu: bir aralığın adı olması, o aralığı çalışma zamanında var eden bir şey değil.
Kapsam ile Yaşam Süresi Arasındaki Fark
Programlama Temelleri kursu (M01/K02) kapsam (scope) kavramını kurmuştu: bir adın hangi
blokta göründüğü ve o bloktan çıkınca erişilemez hâle geldiği. Yaşam süresi bu kavramla
ilişkili ama aynı şey değil. Kapsam sözdizimsel bir sınırdır — kaynaktaki { ve }
çiftleriyle çizilir ve bir bloğun tamamını kapsar. Yaşam süresi ise bir kullanım
aralığıdır: bir referansın gerçekten geçerli olması gereken aralık, içinde bulunduğu bloğun
tamamı değil, yalnızca oluşturulduğu noktadan son kullanıldığı noktaya kadar olan kesimdir.
Aşağıdaki ölçümde bu ayrım somutlaşıyor: r1, r2, r3 referansları bir kapsamın içinde
oluşturuluyor ama o kapsamın tamamını doldurmuyor — son kullanıldıkları satırdan sonra
kapsam kapanana kadar hâlâ birkaç satır kod çalışıyor ve bu satırlar artık referanslara hiç
dokunmuyor. Referansların geçerlilik aralığı oluşturuldukları yerden son kullanıldıkları yere
kadar uzanıyor; kapsamın geri kalanı, üç değerin sahipliği açısından hâlâ önemli (üçü de
kapsam kapanana kadar canlı), ama referanslar açısından artık konu dışı. Bu, yaşam süresinin
neden ayrı bir kavram olarak adlandırıldığını gösteriyor: kapsam tek başına, bir referansın ne
zamana kadar güvenli olduğunu söylemeye yetmiyor.
Bu fark tek yönlü değil: bir referansın aralığı, kapsamın tamamını doldurmak zorunda olmasa da, kapsamın dışına da taşamaz. Bir referans, ait olduğu bloktan çıktıktan sonra hiçbir biçimde kullanılmaya devam edemez — geçerlilik aralığı kapsamın bir alt kümesidir, kapsamdan bağımsız, ondan tamamen kopuk bir çizgi değil. Sonraki derslerde görülecek açık yaşam süresi açıklamaları da bu ilişkiyi bozmuyor; yalnızca birden çok referansın aralığı arasında bir bağ olduğunda, bu bağı kaynağa yazılı hâle getiriyor.
Ölçüm: Bir Referansın Aralığı Sahibin Ömrü İçinde Kalıyor
Yığıt ve Öbek dersindeki ölçüm çekirdeğinin A bölümü üç değerin (kapsam-1, kapsam-2,
kapsam-3) aynı kapsamda kurulup kapsam kapanınca bildirimin tersi sırayla bittiğini
göstermişti. Aynı kesit burada referans alınarak yeniden koşturuluyor: üç değere kapsam
içindeyken birer referans alınıyor, o referanslar üzerinden okuma yapılıyor ve okuma anında
deftere bakılıyor.
// referans_araligi.rs — omur.rs'in A bolumu kesiti: uc deger, referans uzerinden okunuyor.
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(", ")
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
let a0 = okunan(&defter).len();
{
let kapsam_1 = Iz::yeni("kapsam-1", &defter);
let kapsam_2 = Iz::yeni("kapsam-2", &defter);
let kapsam_3 = Iz::yeni("kapsam-3", &defter);
let r1 = &kapsam_1;
let r2 = &kapsam_2;
let r3 = &kapsam_3;
println!("referanslar uzerinden okuma -> {}, {}, {}", r1.ad, r2.ad, r3.ad);
println!("okuma aninda defter -> [{}]", yeni_olanlar(&defter, a0));
// Referanslarin son kullanimi yukaridaki satirdaydi; asagida hicbiri gecmiyor ama
// uc deger de kapsam kapanana kadar sahibi olarak yasamaya devam ediyor.
let hala_bos = yeni_olanlar(&defter, a0).is_empty();
println!("kapsam hala acikken defter bos mu -> {}", hala_bos);
}
println!("kapsam kapaninca -> {}", yeni_olanlar(&defter, a0));
}
referanslar uzerinden okuma -> kapsam-1, kapsam-2, kapsam-3 okuma aninda defter -> [] kapsam hala acikken defter bos mu -> true kapsam kapaninca -> kapsam-3, kapsam-2, kapsam-1
r1, r2, r3 referansları oluşturulup okunduğu anda defter hâlâ boş — üç değer de henüz
canlı, çünkü kapsam henüz kapanmadı. Referansların son kullanımı da tam bu satırda: bir daha
kullanılmıyorlar. Ardından gelen satır referanslara hiç dokunmadan defterin hâlâ boş olduğunu
doğruluyor — üç değer de, referanslar iş bitirdikten sonra bile, kapsamın kendi sonuna kadar
sahip olarak yaşamaya devam ediyor. Kapsam kapandığında bildirimin tersi sırayla bitiyorlar —
Yığıt ve Öbek dersinde kurulan kural burada da geçerli. Referansların kendi geçerlilik aralığı
bu üç bağlamanın ömrünün içine yerleşiyor; dışına taşmıyor, ama bağlamaların ömrünün
tamamını doldurmak zorunda da değil.
YS2. Bir referansın geçerlilik aralığı, referans verdiği değerin ömrünün bir alt aralığıdır — referans, sahibinin bittiği noktadan sonra hiçbir zaman kullanılamaz. Bu sınırı aşan bir yazım derleyici tarafından reddedilir; reddin somut biçimi Ödünç Denetleyicisi dersinde koşturularak gösterilmişti.
Yukarıdaki ölçümde r1 gibi bir referansın iç kapsamın dışına taşınmaya çalışıldığını
düşünelim: r1’in dış kapsamda tanımlı bir değişkene atanması ya da bir işlevden döndürülmesi
istensin. Bu yazım derlenmez, çünkü r1’in bağlı olduğu kapsam_1 iç kapsam kapanınca
bitiyor ve r1 o noktadan sonra da yaşamaya devam eden bir yerde tutulmuş olurdu — bir
referansın kendi sahibinden uzun yaşaması, tam olarak yaşam süresinin izin vermediği durum.
Yukarıdaki koşum kabul edildi çünkü üç referans da yalnızca kendi iç kapsamlarının içinde
kaldı; kabul edilenle reddedilen arasındaki fark, referansın kullanım yerinin sahibin
kapsamının içinde mi dışında mı olduğu.
Sınırlayıcı Ölçüm: Aralık İç İçe Olmak Zorunda Değil
Ders kitaplarında yaşam süresi sık sık iç içe geçmiş kutular gibi çizilir ve bu çizim yanlış bir izlenim bırakabilir: sanki her referansın aralığı bir üst aralığın içine yuvalanmak zorundaymış gibi. Oysa iki bağımsız referansın aralığının birbiriyle hiçbir ilişkisi olmak zorunda değil — hatta hiç kesişmeleri bile gerekmiyor.
// bagimsiz_araliklar.rs — iki referansin gecerlilik araligi hic kesismiyor.
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 main() {
let defter: Defter = Rc::new(RefCell::new(Vec::new()));
let iz_a = Iz::yeni("bagimsiz-a", &defter);
let iz_b = Iz::yeni("bagimsiz-b", &defter);
{
let r = &iz_a;
println!("a uzerinden okuma -> {}", r.ad);
}
println!("a okunduktan sonra defter -> [{}]", okunan(&defter).join(", "));
{
let r = &iz_b;
println!("b uzerinden okuma -> {}", r.ad);
}
println!("b okunduktan sonra defter -> [{}]", okunan(&defter).join(", "));
}
a uzerinden okuma -> bagimsiz-a a okunduktan sonra defter -> [] b uzerinden okuma -> bagimsiz-b b okunduktan sonra defter -> []
iz_a ve iz_b aynı dış kapsamda birlikte yaşıyor ve ikisi de yalnız programın en sonunda
bitiyor — defter iki okumadan sonra da boş kalıyor, çünkü henüz hiçbiri bitmedi. Ama
referanslara bakıldığında tablo farklı: iz_a’ya alınan referans kendi iç bloğunda
oluşturulup orada kullanılıyor ve o blok kapanınca geçerliliğini yitiriyor; iz_b’ye alınan
referans ondan sonra, tamamen ayrı bir blokta oluşturuluyor. İki referansın geçerlilik
aralığı zaman içinde hiç üst üste binmiyor — biri tamamen kapandıktan sonra öbürü açılıyor.
Derleyici bu iki aralığı hiçbir noktada karşılaştırmak zorunda kalmıyor, çünkü aralarında
hiçbir ilişki kurulmadı.
YS3. İki referansın geçerlilik aralığı iç içe olmak ya da kesişmek zorunda değildir; birbirinden tamamen bağımsız, hatta zaman içinde hiç örtüşmeyen aralıklar aynı programda yan yana bulunabilir. Bir işlev imzası iki referans arasında bir ilişki kurmadığı sürece, o iki aralık derleyici için de birbirinden bağımsız kalır — bu ilişkinin ne zaman kurulması gerektiği sıradaki dersin konusu.
Bu bağımsızlığın pratik karşılığı şu: bir programda aynı anda onlarca referans yaşayabilir ve
bunların büyük çoğunluğunun birbiriyle hiçbir ilişkisi yoktur — her biri kendi bağımsız
aralığında, kendi sahibine bağlı olarak var olur. Derleyici yalnızca gerçekten ilişkili olan
aralıkları birbiriyle karşılaştırır: bir referans başka bir referansın verisine dayanıyorsa,
ya da bir işlev iki referans arasında bir bağ kuruyorsa. İlişkisiz iki referans hiçbir zaman
bu karşılaştırmaya girmez, tıpkı yukarıdaki iz_a ve iz_b örneğinde olduğu gibi. Bu, bir
sonraki derste görülecek açık yazımın neden yalnızca gerektiğinde ortaya çıktığını da
açıklıyor: bir ilişki yoksa yazılacak bir şey de yok.
Aralığın Kaynaktan Okunması
Yukarıdaki iki ölçümün ortak bir özelliği var: hiçbiri bir yaşam süresini doğrudan basmadı.
Basılan tek şey, sahip olunan değerlerin ne zaman bittiğiydi — referansların kendi aralığı
hiçbir çıktıda görünmedi, çünkü görünecek bir çalışma zamanı karşılığı yok. Bir referansın
aralığını öğrenmenin tek yolu kaynağı okumak: r1’in aralığı, let r1 = &kapsam_1;
satırından r1.ad’ın kullanıldığı yazdırma çağrısına kadar uzanıyor; bunun ne öncesinde ne
sonrasında ayrı bir araç çalıştırmaya gerek var.
Bu, kursun önceki derslerinde (ifade/deyim/öğe sınıflandırması, dört bitiş sınıfı) kurulan kalıbın devamı: her ölçüm, koşum bittikten sonra elde kalan bir sayı ya da sıralı bir kayıt değil, kaynağın kendi yapısından çıkarılan bir sonuç üretiyor. Yaşam süresi için bu, çıktıda hiç görünmeyen ama kaynakta her zaman görünür olan bir bilgi anlamına geliyor.
İkinci ölçümde de aynı okuma yöntemi geçerli: iz_a üzerindeki referansın aralığını bulmak
için deftere ya da bir başka çıktıya bakmaya gerek yok — kaynakta let r = &iz_a; satırından
r.ad’ın kullanıldığı satıra kadar olan iç blok, o aralığın tamamı. iz_b’ninki de aynı
biçimde, kendi iç bloğuyla sınırlı. İki referansın aralığı arasında kalan satırlar — birinci
blok kapandıktan sonra, ikinci blok açılmadan önceki satır — her iki aralığa da ait değil ve
bu da yalnızca kaynağı okuyarak anlaşılıyor. Bir çalıştırma aracı ya da ek bir araç bu
sonuca hiçbir katkı yapmıyor; ölçüm çekirdeği burada yalnızca zaten doğru olan bir okumayı
somutlaştırmak için var.
Özet
- Yaşam süresi (lifetime), bir referansın geçerli kabul edildiği kaynak aralığının adıdır ve bir çalışma zamanı nesnesi değildir — yalnız derleyicinin kaynak üzerinde yürüttüğü bir çıkarımdır.
- Bir referans
Dropuygulamaz ve deftere hiçbir zaman düşmez; yalnız sahip olunan değerler düşer; sahip olunan değerin ömrü kapsam sonunda bitiyor. - Bir referansın geçerlilik aralığı, referans verdiği değerin ömrünün bir alt aralığıdır ve bu aralık sahibinin bitişinden sonrasına taşamaz.
- İki bağımsız referansın geçerlilik aralığı iç içe olmak ya da kesişmek zorunda değildir.
- Bir referansın aralığı hiçbir çıktıda görünmez; tek kaynağı, referansın oluşturulduğu ve son kullanıldığı satırların kaynak koddaki yeridir.
Sonraki Adım
Bu derste bir referansın aralığının sahibinin ömrü içinde kaldığı, bu aralığın çalışma zamanında hiçbir iz bırakmadığı ve iki bağımsız aralığın birbiriyle hiçbir ilişkiye girmeyebileceği görüldü. Ama bazı işlevler tam olarak böyle bir ilişki kurmak zorunda: birden çok referans alıp içlerinden birini döndüren bir işlev yazıldığında, dönen referansın hangi girdiye bağlı olduğu kaynaktan tek başına çıkarılamıyor ve derleyici bu belirsizliği kabul etmiyor. Sıradaki ders bu durumda aralığın nasıl açıkça yazıldığını, bu yazının kaynağı hangi bilgiyle zenginleştirdiğini ve yazılan şeyin gerçekte tutmadığında ne olduğunu ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.