Herkese selamlar,
Lifetime konusu Rust'ta bir türlü kavrayamadığım bir konuydu. Ben de bu konunun üzerine düşmeye karar verdim ve internette taradığım kaynaklardan anladıklarımı örnek kodlar yazarak özetledim. Özetimi buraya bırakıyorum. Kıymetli geri bildirimleriniz için şimdiden teşekkürler.
Lifetime
Rust'ta her referansın bir yaşam ömrü(lifetime) vardır. Çoğu zaman lifetime referansın bulunduğu kapsam(scope) ile sınırlıdır. Bu yüzden belirtmeye gerek yoktur, derleyici(compiler) lifetime'ı kendisi çıkarsayabilir. Sadece birden fazla lifetime mümkün olduğunda lifetime'lar belirtilmelidir. Tıpkı değişken tanımlarken her zaman veri tipini belirtmemize gerek olmadığı gibi. 1
Peki neden referansların lifetime'ı var ve neden bazı durumlarda bunu belirtmemiz gerekiyor?
- Boşluğa işaret eden referans(dangling reference) durumunun önüne geçmek için
- Veriyi güvenli bir şekilde paylaşabilmek için 2
Lifetime Annotations
Lifetime'ları belirtmek için lifetime belirteçleri(lifetime annotations) kullanırız. Bu belirteçlerin kullanım şekli tıpkı generic tipler gibidir, yalnızca tek bir farkla lifetime annotations'ın başında her zaman tek tırnak(') bulunur.
Lifetime belirteçlerini(lifetime annotations) kullanırken her zaman şunu aklımızda tutmakta fayda var : lifetime annotations referansların lifetime'ını değiştirmez sadece birden fazla referans olduğunda bunların lifetime'ları arasındaki ilişkiyi açıklamamıza yarar. 1 3 4
Kod örnekleri
İlk önce x ve y adında farklı scope'larda iki tamsayı değişkeni tanımlayalım. Değerlerinin hiç önemi yok.
fn main() {
let x = 5; // -> 2
{
let y = 3; // -> 5
} // -> 6
} // -> 7
Yorumlarda satır numaralarını işaretledim.
Burada x'in lifetime'ı 2'de başlıyor, 7'de son buluyor. y'nin lifetime'ı ise 5'de başlıyor, 6'da son buluyor.
Şimdi de &i32 tipinde bir z referansı tanımlayalım.
fn main() {
let x = 5; // -> 2
let z: &i32; // -> 3
{
let y = 3;
}
} // -> 8
z'nin ve x in lifetime'ı 8. satırda son buluyor.
Sonrasında get_x_or_y() adında bir fonksiyon tanımlayalım. Bu fonksiyon x ve y'nin referansını alsın ve bir i32
referansı döndürsün. Döndürdüğü değeri de z'ye eşitleyelim.
fn main() {
let x = 5;
let z: &i32;
{
let y = 3;
z = get_x_or_y(&x, &y);
}
println!("z={}", z);
}
fn get_x_or_y (x: &i32, y: &i32) -> &i32 {
x
}
Kod bu haliyle derlenmeyecektir çünkü bizden lifetime annotations bekliyor.
error[E0106]: missing lifetime specifier
--> src\main.rs:12:37
|
12 | fn get_x_or_y (x: &i32, y: &i32) -> &i32 {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
12 | fn get_x_or_y<'a> (x: &'a i32, y: &'a i32) -> &'a i32 {
| ++++ ++ ++ ++
Derleyici bu konuda oldukça haklı çünkü biz hem x'i hem de y'yi ödünç aldık. Geriye döndüreceğimiz değerin lifetime'ının
en az z'nin lifetime'ı kadar olacağını taahüt etmemiz gerekiyor. Aksi halde boşluğa işaret eden referans(dangling reference)
döndürme ihtimalimiz var.
O zaman önce lifetime annotation'larını kullanarak kodu çalışabilir hale getirelim. x'in lifetime'ı için 'a ve y'nin lifetime'ı için 'b annotation'larını kullanalım. x'i geri döndüreceğimiz için metot imzasında geri dönüş tipinin lifetime'ı için de 'a annotation'ını kullanmalıyız.
fn main() {
let x = 5;
let z: &i32;
{
let y = 3;
z = get_x_or_y(&x, &y);
}
println!("z={}", z);
}
fn get_x_or_y<'a, 'b> (x: &'a i32, y: &'b i32) -> &'a i32 {
x
}
Bu şekilde çalıştı.
Peki derleyici y'yi geri döndürmemize izin verir mi?
fn main() {
let x = 5;
let z: &i32;
{
let y = 3;
z = get_x_or_y(&x, &y);
}
println!("z={}", z);
}
fn get_x_or_y<'a, 'b> (x: &'a i32, y: &'b i32) -> &'b i32 {
y
}
error[E0597]: `y` does not live long enough
--> src\main.rs:7:28
|
7 | z = get_x_or_y(&x, &y);
| ^^ borrowed value does not live long enough
8 | }
| - `y` dropped here while still borrowed
9 | println!("z={}", z);
| - borrow later used here
Tahmin ettiğimiz gibi izin vermedi. Çünkü y'nin lifetime'ı 8. satırda bitiyor ancak biz değerini y'den ödünç aldığımız
z değişkenini 9. satırda kullanıyoruz.
Ancak z'yi y'nin lifetime'ı içine taşırsak kod çalışacaktır.
fn main() {
let x = 5;
let z: &i32;
{
let y = 3;
z = get_x_or_y(&x, &y);
println!("z={}", z);
}
}
fn get_x_or_y<'a, 'b> (x: &'a i32, y: &'b i32) -> &'b i32 {
y
}