Rust'ta lifetime (yaşam süresi), bir referansın geçerli olduğu kapsamdır. Çoğu zaman derleyici bunu kendisi çıkarır; ancak bir fonksiyon birden fazla referans alıp referans döndürdüğünde ya da bir struct referans sakladığında, referanslar arasındaki ilişkiyi 'a gibi genel lifetime parametreleriyle yazmanız gerekir. Bu notasyon hiçbir değerin ömrünü uzatmaz veya kısaltmaz; yalnızca borrow checker'a hangi referansın hangisinden daha uzun yaşamaması gerektiğini söyler.
Bu yazıda sarkan referansların nasıl yakalandığını, longest örneği üzerinden lifetime parametrelerinin anlamını, struct'larda referans saklamayı, derleyicinin çoğu imzada lifetime yazmanızı gereksiz kılan üç elision kuralını ve 'static etrafındaki yaygın yanlış anlamaları çalışan örneklerle anlatıyoruz. Referansların temel kuralları için Rust referans ve borrowing yazısına bakabilirsiniz.
Lifetime neden var: sarkan referansı önlemek

Lifetime'ın temel amacı, bir programın gösterdiği veri artık yokken referansı kullanmasını engellemektir. Aşağıdaki kodda dış kapsamda tanımlanan r, iç kapsamdaki x'e referans alır; iç kapsam bitince x düşürülür ama r hâlâ kullanılmak istenir:
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {r}");
}
// error[E0597]: `x` does not live long enoughDerleyicinin mesajı üç yeri işaretler: x'in tanımlandığı satır, ödünç alındığı yer ve x'in hâlâ ödünç verilmişken düşürüldüğü kapanış parantezi. The Book bunu borrow checker'ın kapsamları karşılaştırması olarak anlatır: r'nin ömrüne 'a, x'inkine 'b derseniz, 'b daha kısa olduğu için referansın konusu referanstan uzun yaşamıyor ve program reddediliyor. Değişken tanımlarını aynı kapsama aldığınızda x referanstan uzun yaşar ve kod derlenir.
Bu örnekte let r; satırı ilk değer vermeden tanımlama yapar. Bu, Rust'ta null olduğu anlamına gelmez: değer verilmemiş bir değişkeni kullanmaya çalışırsanız derleme hatası alırsınız.
Fonksiyonlarda genel lifetime parametresi
İki metin dilimini alıp uzun olanı döndüren bir fonksiyon düşünelim. İlk deneme derlenmez:
fn en_uzun(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
println!("{}", en_uzun("abcd", "xyz"));
}
// error[E0106]: missing lifetime specifierYardım notu sorunu açıkça söyler: dönüş tipi ödünç alınmış bir değer içeriyor ama imza bunun x'ten mi yoksa y'den mi ödünç alındığını söylemiyor. Fonksiyonu yazan siz de bilemezsiniz, çünkü if dalı x'i, else dalı y'yi döndürür. Çözüm, ilişkiyi bir lifetime parametresiyle ifade etmektir:
fn en_uzun<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let metin1 = String::from("uzun bir metin");
{
let metin2 = String::from("kısa");
let sonuc = en_uzun(metin1.as_str(), metin2.as_str());
println!("en uzun: {sonuc}");
}
}
// çıktı: en uzun: uzun bir metinLifetime parametreleri kesme işaretiyle başlar, genellikle kısa ve küçük harflidir; ilk parametreye çoğu zaman 'a denir. Referans tipinde & işaretinden sonra yazılır: &'a i32 ya da &'a mut i32. Fonksiyon adından sonra açılı parantez içinde, tıpkı genel tip parametreleri gibi tanımlanır.
İmzanın söylediği şudur: bir 'a ömrü için, her iki parametre de en az 'a kadar yaşar ve dönen referans da en az 'a kadar geçerlidir. Gerçek çağrıda 'a, iki argümanın ömürlerinden kısa olanına eşitlenir. Yukarıdaki örnekte metin2 iç kapsamda yaşadığı için sonuc da en fazla iç kapsam boyunca kullanılabilir; zaten orada kullanıldığı için sorun yoktur.
Kısa ömür kuralının sonucu
Şimdi sonuc'u dış kapsamda tanımlayıp iç kapsamda atayalım ve iç kapsam bittikten sonra kullanalım. İnsan gözüyle bakınca daha uzun olan metin1 döneceği için güvenli görünür, ama derleyici bu kodu kabul etmez:
fn en_uzun<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let metin1 = String::from("uzun bir metin");
let sonuc;
{
let metin2 = String::from("kısa");
sonuc = en_uzun(metin1.as_str(), metin2.as_str());
}
println!("en uzun: {sonuc}");
}
// error[E0597]: `metin2` does not live long enoughDerleyici fonksiyonun gövdesine değil imzasına bakar. İmzada dönen referansın ömrü iki girdiden kısa olanına bağlandığı için, sonuc'un println! satırında geçerli olabilmesi metin2'nin de o satıra kadar yaşamasını gerektirir. The Book bunun bilinçli bir tercih olduğunu vurgular: lifetime notasyonu fonksiyonun sözleşmesinin bir parçasıdır, tıpkı parametre tipleri gibi. Sözleşme imzada durduğu için hata mesajları da sorunun kaynağını daha isabetli gösterebilir.
Lifetime yalnızca gerçekten ilişkili parametrelere yazılır
Lifetime'ı nereye yazacağınız fonksiyonun ne yaptığına bağlıdır. Fonksiyon her zaman ilk parametreyi döndürüyorsa ikinci parametrenin dönüş değeriyle hiçbir ilişkisi yoktur ve ona lifetime yazmanız gerekmez:
fn ilki<'a>(x: &'a str, _y: &str) -> &'a str {
x
}
fn main() {
let a = String::from("abcd");
let sonuc;
{
let b = String::from("efghijklmnop");
sonuc = ilki(a.as_str(), b.as_str());
}
println!("{sonuc}"); // b düşürüldü ama sonuc yalnızca a'ya bağlı
}
// çıktı: abcdBir önceki hatalı örnekle karşılaştırın: bu kez sonuc yalnızca a'ya bağlı olduğu için b'nin erken düşürülmesi sorun değildir. Dönüş tipindeki lifetime, parametrelerden birininkiyle eşleşmek zorundadır. Eşleşmiyorsa dönen referans fonksiyon içinde oluşturulmuş bir değeri gösteriyor demektir ve bunu hiçbir lifetime notasyonu kurtaramaz:
fn uydur<'a>(_x: &str) -> &'a str {
let sonuc = String::from("gerçekten uzun bir metin");
sonuc.as_str()
}
fn main() {
println!("{}", uydur("x"));
}
// error[E0515]: cannot return value referencing local variable `sonuc`Bu durumda en iyi çözüm referans yerine sahipli bir değer, burada String, döndürmektir. Böylece değeri temizleme sorumluluğu çağırana geçer.
Struct içinde referans saklamak
Struct'lar sahipli veri yerine referans da tutabilir; ama o zaman her referans alanına lifetime yazmanız gerekir. Lifetime parametresi struct adından sonra tanımlanır ve şu anlama gelir: bu struct'ın bir örneği, alanındaki referansın gösterdiği veriden daha uzun yaşayamaz.
struct Alinti<'a> {
parca: &'a str,
}
impl<'a> Alinti<'a> {
fn seviye(&self) -> i32 {
3
}
fn duyur_ve_dondur(&self, duyuru: &str) -> &str {
println!("Dikkat: {duyuru}");
self.parca
}
}
fn main() {
let roman = String::from("Bana İsmail deyin. Yıllar önce...");
let ilk_cumle = roman.split('.').next().unwrap();
let a = Alinti { parca: ilk_cumle };
println!("{}", a.seviye());
println!("{}", a.duyur_ve_dondur("alıntı geliyor"));
}
// çıktı: 3
// çıktı: Dikkat: alıntı geliyor
// çıktı: Bana İsmail deyinimpl<'a> Alinti<'a> satırındaki tanım zorunludur, çünkü struct'ın lifetime'ı onun tipinin bir parçasıdır. Metot imzalarında ise çoğu zaman lifetime yazmanız gerekmez; nedeni bir sonraki bölümdeki elision kurallarıdır.
Lifetime elision: üç kural

İlk bölümdeki dilim örneklerinde fn ilk_kelime(s: &str) -> &str gibi imzalar lifetime yazmadan derleniyordu. The Book'a göre Rust 1.0 öncesinde her referansa açık lifetime yazmak gerekiyordu; ekip aynı kalıpların tekrar tekrar yazıldığını görünce bu kalıpları derleyiciye öğretti. Bu kalıplara lifetime elision kuralları denir. Bunlar sizin uymanız gereken kurallar değil, derleyicinin denediği belirli durumlardır; kod bu durumlardan birine uyuyorsa açık notasyon gerekmez. Parametrelerdeki lifetime'lara girdi, dönüş değerindekilere çıktı lifetime'ı denir.
| Kural | Ne yapar | Örnek açılım |
|---|---|---|
| 1. kural | Referans olan her parametreye ayrı bir lifetime verir | fn f(x: &i32, y: &i32) olur fn f<'a, 'b>(x: &'a i32, y: &'b i32) |
| 2. kural | Tek bir girdi lifetime varsa onu tüm çıktılara atar | fn f(x: &str) -> &str olur fn f<'a>(x: &'a str) -> &'a str |
| 3. kural | Metotta &self veya &mut self varsa onun lifetime değerini çıktılara atar | fn f(&self, d: &str) -> &str çıktısı self ile bağlanır |
Kuralları derleyici gibi uygulayalım. fn ilk_kelime(s: &str) -> &str imzasında birinci kural tek parametreye 'a verir ve imza fn ilk_kelime<'a>(s: &'a str) -> &str olur. Şimdi tam bir girdi lifetime'ı olduğu için ikinci kural devreye girer ve çıktıya da aynı 'a atanır. İmzadaki bütün referansların lifetime'ı belli olduğundan derleyici analizine açık notasyon olmadan devam eder. Kurallar yalnızca fonksiyon tanımlarında değil impl bloklarında da uygulanır.
Üç kural uygulandıktan sonra hâlâ lifetime'ı belirlenemeyen bir referans kalırsa derleyici tahmin yürütmez, hata verir. en_uzun fonksiyonunda tam olarak bu oldu: birinci kural iki ayrı lifetime üretti, ikinci kural birden fazla girdi olduğu için uygulanamadı, üçüncü kural da self olmadığı için devreye girmedi.
Üçüncü kural metotlarda okumayı kolaylaştırır ama bir tuzağı vardır: çıktı otomatik olarak self'e bağlanır. Metot self yerine diğer parametreyi döndürmeye çalışırsa derleyici imzanın vaadiyle gövdenin uyuşmadığını söyler:
struct Alinti<'a> {
parca: &'a str,
}
impl<'a> Alinti<'a> {
fn duyur(&self, duyuru: &str) -> &str {
println!("{}", self.parca);
duyuru
}
}
fn main() {}
// error: lifetime may not live long enoughDerleyicinin önerisi, dönüş değerini duyuru parametresine bağlayan ayrı bir lifetime eklemektir: fn duyur<'b>(&self, duyuru: &'b str) -> &'b str. Rust Reference ayrıca '_ yer tutucusunu tanımlar; elision kurallarıyla aynı çıkarımı yapar ve özellikle yol içinde, örneğin Alinti<'_> gibi tip argümanlarında tercih edilir:
struct Alinti<'a> {
parca: &'a str,
}
fn ilk_kisim(metin: &str) -> Alinti<'_> {
Alinti { parca: &metin[..4] }
}
fn main() {
let m = String::from("Rust öğreniyorum");
println!("{}", ilk_kisim(&m).parca);
}
// çıktı: Rust'static: iki farklı anlam
'static, etkilediği referansın programın kalan süresi boyunca yaşayabileceğini belirten özel bir lifetime'dır. Tüm string literal'ler &'static str tipindedir, çünkü metin doğrudan çalıştırılabilir dosyanın içinde durur. Rust by Example'a göre static ile tanımlanan bir değere alınan referans da 'static'tir ve gerektiğinde daha kısa bir ömre zorlanabilir (coerce).
Kafa karışıklığı, 'static'in trait sınırı olarak kullanıldığı yerde başlar. T: 'static "T sonsuza kadar yaşar" demek değildir; "T, 'static olmayan hiçbir referans içermez" demektir. Sahipli her veri bu sınırı geçer, ama yerel bir değişkene alınan referans geçmez:
use std::fmt::Debug;
fn yazdir(girdi: impl Debug + 'static) {
println!("{girdi:?}");
}
fn main() {
let i = 5;
yazdir(i); // sahipli değer: sorun yok
yazdir(&i); // main içindeki yerel değişkene referans: 'static değil
}
// error[E0597]: `i` does not live long enoughLifetime sınırları: 'a: 'b
Genel tiplerde olduğu gibi lifetime'lara da sınır konabilir. 'a: 'b ifadesi "'a en az 'b kadar uzun yaşar" diye okunur. Bu sayede uzun ömürlü bir referansı daha kısa ömürlü bir referans olarak döndürebilirsiniz. Genel bir tip için T: 'a sınırı ise T içindeki tüm referansların 'a'dan uzun yaşaması gerektiğini söyler.
fn ilkini_sec<'a: 'b, 'b>(ilk: &'a i32, _ikinci: &'b i32) -> &'b i32 {
ilk // 'a, 'b'den uzun olduğu için 'b'ye zorlanabilir
}
fn main() {
let ilk = 2;
{
let ikinci = 3;
println!("{}", ilkini_sec(&ilk, &ikinci));
}
}
// çıktı: 2Generic tip, trait sınırı ve lifetime bir arada
Lifetime'lar da bir tür genel parametre olduğundan, tip parametreleriyle aynı açılı parantez listesinde tanımlanır. Aşağıdaki fonksiyon en_uzun'a, Display uygulayan herhangi bir tipte bir duyuru parametresi ekler:
use std::fmt::Display;
fn duyurulu_en_uzun<'a, T>(x: &'a str, y: &'a str, duyuru: T) -> &'a str
where
T: Display,
{
println!("Duyuru: {duyuru}");
if x.len() > y.len() { x } else { y }
}
fn main() {
let s = duyurulu_en_uzun("elma", "karpuz", 42);
println!("{s}");
}
// çıktı: Duyuru: 42
// çıktı: karpuzTrait sınırları ve where sözdiziminin ayrıntıları için Rust trait rehberi, genel tipler için Rust generics yazısına bakabilirsiniz.
Sık yapılan hatalar

- Lifetime'ın değerleri uzattığını sanmak:
'ayazmak hiçbir değişkeni daha uzun yaşatmaz; yalnızca var olan ilişkiyi tarif eder. Veri yeterince yaşamıyorsa çözüm verinin sahibini daha dış bir kapsama taşımaktır. - Her referansa aynı lifetime'ı yazmak: İlişkisiz bir parametreye de
'avermek, çağıranı gereksiz yere kısıtlar. Yalnızca dönüş değerinin gerçekten bağlı olduğu parametreleri bağlayın. - Yerel değere referans döndürmeye çalışmak: E0515 hiçbir notasyonla çözülmez; sahipli değer döndürün.
- Struct'ta gereksiz yere referans tutmak: Uzun yaşayacak yapılarda
&'a stryerineStringsaklamak lifetime parametresinin her yere yayılmasını önler. - Hata mesajındaki 'static önerisini körü körüne uygulamak: Genellikle asıl sorun bir kapsam uyuşmazlığıdır. Ayrıntılı hata çözümleri için E0106, E0597 ve E0515 rehberi yazısına bakın.
Sık Sorulan Sorular
Lifetime çalışma anında bir şey yapar mı?
Hayır. Lifetime'lar yalnızca derleme zamanında borrow checker tarafından kullanılan bilgilerdir. The Book'un bölüm özetinde belirtildiği gibi bu analizin tamamı derleme sırasında yapılır ve çalışma anı performansını etkilemez.
'a adının bir özelliği var mı?
Hayır. 'a bir gelenektir; 'girdi gibi daha açıklayıcı bir ad da verebilirsiniz. İstisna, Rust'ın ayırdığı 'static gibi özel adlar ve çıkarım için kullanılan yer tutucu '_'dir.
Ne zaman açıkça lifetime yazmam gerekir?
Üç elision kuralı bir referansın ömrünü belirleyemediğinde, yani birden fazla referans girdisi olan ve self almayan bir fonksiyon referans döndürdüğünde, ve bir struct ya da enum alanında referans sakladığınızda.
Trait object içeren tiplerde lifetime nasıl çıkarılır?
Box<dyn Trait> gibi tiplerde Rust Reference'ta ayrı tanımlanmış varsayılan nesne lifetime kuralları devreye girer; örneğin Box<dyn Foo>, Box<dyn Foo + 'static> ile aynıdır. Ayrıntılar için Rust trait object (dyn) yazısına bakabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Rust öğrenme rehberi · Referans ve borrowing · Slice ve &str · Lifetime hataları · Generics · Trait tanımlama · Rust nedir?