Rust'taki üç yaygın lifetime hatası farklı şeyler söyler. error[E0106]: missing lifetime specifier, bir fonksiyon imzasında, struct'ta veya tip takma adında referansın hangi değere bağlı olduğunu derleyicinin çıkaramadığını bildirir; çözüm 'a gibi bir lifetime parametresi yazmak veya sahipli tip döndürmektir. error[E0597]: ... does not live long enough, referansı hâlâ kullanılan bir değerin daha önce bırakıldığını söyler; çözüm değeri daha geniş bir kapsamda tanımlamaktır. error[E0515]: cannot return reference to local variable ise fonksiyon bitince yok olacak yerel bir değere referans döndürmeye çalıştığınızı gösterir; çözüm referans yerine değerin kendisini, örneğin String döndürmektir.

Bu yazıda bu üç hatayı ve yakın akrabaları E0716'yı Rust 1.99.1'in gerçek mesajlarıyla inceliyoruz. Lifetime sözdiziminin kendisi ve generic lifetime parametreleri Rust lifetime yazısında ayrıntılı anlatılıyor; burada hatayı gördüğünüzde nasıl teşhis edeceğinize odaklanıyoruz. Aynı anda ödünç alma çakışmaları için E0499 ve E0502, taşınmış değer hataları için E0382 yazılarına bakabilirsiniz.

Önce tek bir cümle: lifetime etiketi ömür uzatmaz

Bu hataları çözerken en çok yapılan yanlış, lifetime etiketlerinin değerleri daha uzun yaşatacağını sanmaktır. The Rust Programming Language kitabı açıkça söyler: lifetime etiketleri hiçbir referansın ne kadar yaşadığını değiştirmez; yalnızca birden çok referansın ömürleri arasındaki ilişkiyi tarif eder. Derleyicinin ödünç alma denetleyicisi bu tarifleri kodun gerçek kapsamlarıyla karşılaştırır. Tarif eksikse E0106, tarif gerçekle çelişiyorsa E0597 veya E0515 alırsınız. Dolayısıyla E0106'da etiket yazmak çözüm olabilir, ama E0597 ve E0515'te etiket eklemek neredeyse hiçbir zaman işe yaramaz; çünkü sorun tarifte değil, değerin gerçekten erken yok olmasındadır.

E0106: missing lifetime specifier

E0106: missing lifetime specifier: Fonksiyon imzasında, Girdi hiç yokken, Struct, enum ve tip takma adlarında

Fonksiyon imzasında

İki referans alıp bir referans döndüren bir fonksiyon, en sık görülen E0106 kaynağıdır:

rust
fn en_uzun(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    println!("{}", en_uzun("elma", "karpuz"));
}
// error[E0106]: missing lifetime specifier

Rust 1.99.1'in yardım satırı sorunu tam olarak tarif eder: dönüş tipi ödünç alınmış bir değer içeriyor, ama imza bu değerin x mi yoksa y mi olduğunu söylemiyor. Derleyici gövdeye bakarak karar vermez; çağıran kodun yalnızca imzaya güvenebilmesi gerekir. Önerilen çözüm, iki girdiyi ve çıktıyı aynı 'a parametresine bağlamaktır. Bu imza şunu söyler: dönen referans, iki girdiden kısa yaşayanı kadar geçerlidir.

rust
fn en_uzun<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Tek referans girişi olduğunda elision kuralı yeter, etiket gerekmez
fn ilk_kelime(s: &str) -> &str {
    s.split(' ').next().unwrap_or("")
}

fn main() {
    println!("{}", en_uzun("elma", "karpuz"));
    println!("{}", ilk_kelime("merhaba dünya"));
}
// çıktı: karpuz
// çıktı: merhaba

ilk_kelime fonksiyonunda etiket gerekmemesinin nedeni lifetime elision kurallarıdır. Kitap üç kural sayar. Birincisi: referans olan her parametreye ayrı bir lifetime parametresi atanır. İkincisi: tam olarak bir girdi lifetime'ı varsa, o lifetime tüm çıktı referanslarına atanır. Üçüncüsü: birden çok girdi lifetime'ı varsa ama biri &self veya &mut self ise, self parametresinin lifetime'ı çıktılara atanır. Derleyici bu üç kuralı uyguladıktan sonra hâlâ lifetime'ı belirsiz bir çıktı referansı kalırsa E0106 verir. en_uzun fonksiyonunda iki girdi olduğu ve fonksiyon bir metot olmadığı için ikinci ve üçüncü kural uygulanamaz.

İmzaHangi kuralSonuç
fn f(x: &str) -> &strİkinci kuralDerlenir, çıktı x ile bağlı
fn f(x: &str, y: &str) -> &strHiçbiri yetmezE0106
fn f(&self, y: &str) -> &strÜçüncü kuralDerlenir, çıktı self ile bağlı
fn f() -> &strGirdi yokE0106; 'static veya String önerilir
fn f<'a>(x: &'a str, y: &str) -> &stry için ayrı lifetime çıkarılırE0106, çıktı yine belirsiz

Son satır hata kodu dizinindeki bir örnektir: yalnızca bir parametreye etiket yazmak yetmez, çünkü diğer parametre de kendi ayrı lifetime'ını alır ve çıktı yine iki aday arasında kalır.

Girdi hiç yokken

Hiç referans parametresi almayan bir fonksiyonun referans döndürmesi için ödünç alacağı bir şey yoktur:

rust
fn varsayilan_mesaj() -> &str {
    "bilinmeyen hata"
}

fn main() {
    println!("{}", varsayilan_mesaj());
}
// error[E0106]: missing lifetime specifier

Rust 1.99.1 burada iki öneri sunar: 'static lifetime kullanmak, ancak bunun bir const veya static değerden ödünç alınmadıkça olağan olmadığını belirterek; ya da büyük olasılıkla daha doğru olan sahipli bir değer, yani String döndürmek. Bu örnekte döndürülen değer bir dizgi sabiti olduğu için 'static doğrudur; kitaba göre tüm dizgi sabitleri programın ikili dosyasında saklanır ve 'static lifetime'a sahiptir:

rust
fn varsayilan_mesaj() -> &'static str {
    "bilinmeyen hata"
}

fn main() {
    println!("{}", varsayilan_mesaj());
}
// çıktı: bilinmeyen hata

Struct, enum ve tip takma adlarında

Struct alanlarında elision yoktur: referans tutan bir alan her zaman açık bir lifetime ister. Hata kodu dizini aynı kuralın enum varyantları ve type takma adları için de geçerli olduğunu gösterir.

rust
struct Alinti {
    metin: &str,
}

fn main() {
    let a = Alinti { metin: "kısa bir alıntı" };
    println!("{}", a.metin);
}
// error[E0106]: missing lifetime specifier

İki çözüm vardır ve hangisinin doğru olduğu tasarıma bağlıdır. Struct gerçekten başka bir yerdeki metnin bir parçasını göstermek içinse lifetime parametresi eklenir; bu durumda struct, gösterdiği metinden uzun yaşayamaz. Struct verinin sahibi olacaksa alanı String yapmak hem etiketi hem de ömür kısıtını ortadan kaldırır:

rust
struct Alinti<'a> {
    metin: &'a str,
}

// Sahipli alternatif: yaşam süresi etiketi gerekmez
struct AlintiSahipli {
    metin: String,
}

fn main() {
    let kaynak = String::from("Uzun bir paragraf. İkinci cümle.");
    let ilk = kaynak.split('.').next().unwrap();
    let a = Alinti { metin: ilk };
    let b = AlintiSahipli { metin: ilk.to_string() };
    println!("{} | {}", a.metin, b.metin);
}
// çıktı: Uzun bir paragraf | Uzun bir paragraf

Bir struct'ın metotlarında ise üçüncü elision kuralı sayesinde çoğu zaman etiket yazmanız gerekmez. Aşağıdaki metot &self ile birlikte ikinci bir referans parametresi aldığı halde derlenir, çünkü dönüş self ile bağlanır:

rust
struct Ayristirici<'a> {
    girdi: &'a str,
}

impl<'a> Ayristirici<'a> {
    // &self ve başka referans varken dönüş, elision ile self'e bağlanır
    fn ilk_satir(&self, _ayrac: &str) -> &str {
        self.girdi.lines().next().unwrap_or("")
    }
}

fn main() {
    let metin = String::from("başlık\ngövde");
    let a = Ayristirici { girdi: &metin };
    println!("{}", a.ilk_satir("\n"));
}
// çıktı: başlık

E0597: does not live long enough

E0597, bir değer bırakıldığı halde ona olan referansın hâlâ kullanılacağı durumda çıkar. Kitabın sarkan referans örneği bunun en sade biçimidir:

rust
fn main() {
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {r}");
}
// error[E0597]: `x` does not live long enough

Mesajın dört etiketi hikâyeyi sırayla anlatır: binding `x` declared here değişkenin tanımlandığı yeri, borrowed value does not live long enough ödünç alındığı yeri, `x` dropped here while still borrowed iç bloğun kapandığı yeri ve borrow later used here referansın sonradan kullanıldığı yeri gösterir. x iç bloğun sonunda bırakılıyor, ama r bloğun dışında hâlâ okunmak isteniyor. Çözüm, değeri referanstan en az onun kadar uzun yaşayacak bir kapsama taşımaktır:

rust
fn main() {
    let x = 5;
    let r;
    {
        r = &x;
    }
    println!("r: {r}");
}
// çıktı: r: 5

Lifetime parametreli fonksiyonlarla E0597

Aynı hata, imzası doğru yazılmış bir fonksiyonu yanlış kapsamlarla çağırdığınızda da çıkar. en_uzun imzası sonucun iki girdiden kısa yaşayanı kadar geçerli olduğunu söylüyordu. s2 iç blokta bırakıldığı için sonuç da en fazla o bloğun sonuna kadar geçerlidir:

rust
fn en_uzun<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("uzun bir metin");
    let sonuc;
    {
        let s2 = String::from("kısa");
        sonuc = en_uzun(s1.as_str(), s2.as_str());
    }
    println!("en uzun: {sonuc}");
}
// error[E0597]: `s2` does not live long enough

Bu örnekte sonucun aslında s1 olacağını siz bilirsiniz, ama derleyici çalışma anındaki değerlere değil, imzadaki ilişkiye bakar. İmza, sonucun s2 olabileceğini söylediği sürece hata doğru bir uyarıdır. s2 değişkenini dış kapsama aldığımızda kod derlendi ve en uzun: uzun bir metin yazdı. Başka bir seçenek, sonucu iç blokta kullanmak veya sonucun kopyasını almaktır (to_string()).

E0515: yerel değere referans döndürmek

E0515: yerel değere referans döndürmek

Fonksiyonun içinde oluşturduğunuz bir değer, fonksiyon bittiğinde bırakılır. Ona referans döndürmek, çağırana artık var olmayan bir şeyi göstermek olurdu:

rust
fn selam(ad: &str) -> &str {
    let metin = format!("Merhaba, {ad}!");
    &metin
}

fn main() {
    println!("{}", selam("Deniz"));
}
// error[E0515]: cannot return reference to local variable `metin`

Burada lifetime etiketi eklemek işe yaramaz. İmzayı fn selam<'a>(ad: &'a str) -> &'a str yaptığımızda aynı E0515 hatasını aldık, çünkü metin hâlâ fonksiyonun sonunda yok oluyor. Doğru çözüm, sahipliği çağırana devretmektir; hata kodu dizini de yerel veriyi sahiplenen bir değer döndürmeyi önerir:

rust
fn selam(ad: &str) -> String {
    format!("Merhaba, {ad}!")
}

fn main() {
    println!("{}", selam("Deniz"));
}
// çıktı: Merhaba, Deniz!

Aynı hata bir referans içeren tip döndürdüğünüzde de çıkar. Yerel bir vektör üzerinde iter() çağırıp iterator'ü döndürmek, vektöre referans tutan bir değer döndürmek demektir; mesaj bu kez cannot return value referencing local variable `v` olur:

rust
fn cift_sayilar() -> std::slice::Iter<'static, i32> {
    let v = vec![2, 4, 6];
    v.iter()
}

fn main() {
    for n in cift_sayilar() {
        println!("{n}");
    }
}
// error[E0515]: cannot return value referencing local variable `v`

Çözüm, vektörü sahiplenen iterator'ü döndürmektir. into_iter() vektörü tüketir ve döndürülen IntoIter<i32> verinin sahibi olur:

rust
fn cift_sayilar() -> std::vec::IntoIter<i32> {
    let v = vec![2, 4, 6];
    v.into_iter()
}

fn main() {
    for n in cift_sayilar() {
        println!("{n}");
    }
}
// çıktı: 2
// çıktı: 4
// çıktı: 6

Mini örnek: dosyadan okunan metne referans döndürmek

E0515 en çok, bir fonksiyonun kendi okuduğu veya ürettiği veriden bir parça döndürmek istediği gerçek kodlarda çıkar. Aşağıdaki fonksiyon bir ayar dosyasını okuyup ilk satırını döndürmeye çalışıyor. lines() ve next() zinciri icerik değişkenine referans tutan bir &str üretir, ama icerik fonksiyon bitince bırakılır:

rust
use std::fs;

fn sunucu_adi(yol: &str) -> &str {
    let icerik = fs::read_to_string(yol).unwrap_or_default();
    icerik.lines().next().unwrap_or("")
}

fn main() {
    println!("{}", sunucu_adi("ayar.txt"));
}
// error[E0515]: cannot return value referencing local variable `icerik`

Dikkat ederseniz bu imza E0106 vermedi; tek bir referans parametresi olduğu için elision kuralı dönüşü yol parametresine bağladı. Derleyici imzayı kabul etti ama gövdenin bu sözü tutmadığını gördü: dönen dilim yol değil icerik içinden geliyor. Burada iki temiz tasarım var. Birincisinde metnin sahibi çağırandır; fonksiyon yalnızca ödünç aldığı metinden bir parça döndürür ve elision kuralı bu kez doğru ilişkiyi kurar. İkincisinde fonksiyon sonucu sahipli bir String olarak döndürür:

rust
// Tasarım 1: metnin sahibi çağıran; fonksiyon yalnızca ödünç alır
fn sunucu_adi(icerik: &str) -> &str {
    icerik.lines().next().unwrap_or("")
}

// Tasarım 2: fonksiyon kendi okuduğu veriden sahipli bir değer döndürür
fn sunucu_adi_sahipli(icerik: String) -> String {
    icerik.lines().next().unwrap_or("").to_string()
}

fn main() {
    let icerik = String::from("web01\nport=8080");
    println!("{}", sunucu_adi(&icerik));
    println!("{}", sunucu_adi_sahipli(icerik));
}
// çıktı: web01
// çıktı: web01

Birinci tasarım kopya yapmaz ve çağıran metni başka işler için de kullanabilir; ikincisi daha basit bir arayüz sunar ama küçük bir kopya üretir. Dosya okuma ve hata yönetimini birlikte ele almak için std::fs ile dosya okuma ve yazma yazısına bakabilirsiniz.

Yakın akraba: E0716 geçici değer

Bir fonksiyon çağrısının sonucunu doğrudan ödünç aldığınızda derleyici bir geçici değer oluşturur. Geçici değerler normalde içinde bulundukları deyimin sonunda bırakılır. Ondan türetilen referansı sonraki satırda kullanmak E0716 verir:

rust
fn ayni(x: &String) -> &str {
    x.as_str()
}

fn main() {
    let p = ayni(&String::from("geçici"));
    println!("{p}");
}
// error[E0716]: temporary value dropped while borrowed

Derleyici let ile daha uzun yaşayan bir değer oluşturmayı önerir. Değere bir ad verdiğinizde o değer bloğun sonuna kadar yaşar:

rust
fn ayni(x: &String) -> &str {
    x.as_str()
}

fn main() {
    let deger = String::from("kalıcı");
    let p = ayni(&deger);
    println!("{p}");
}
// çıktı: kalıcı

Hata kodu dizini bir ayrıntıyı daha not eder: let x = &foo(); gibi, referansın doğrudan bir değişkene bağlandığı basit durumlarda derleyici geçici değerin ömrünü bloğun sonuna kadar kendisi uzatır. Sorun, geçici değer bir fonksiyon çağrısının içinden geçtiğinde ortaya çıkar.

'static ne zaman doğru çözüm?

Derleyici bazen 'static önerir ve bu öneriyi her yere uygulamak cazip gelir. Kitap bu konuda nettir: 'static yazmadan önce referansın gerçekten programın tüm ömrü boyunca yaşayıp yaşamadığını düşünün. Kitaba göre 'static öneren hata mesajlarının çoğu, sarkan bir referans oluşturma girişiminden veya mevcut lifetime'ların uyuşmamasından kaynaklanır ve çözüm bu sorunları düzeltmektir. Dizgi sabitleri, static değişkenler ve const değerler için 'static doğrudur; çalışma anında oluşturulan bir String içinse genellikle sahipli tip döndürmek gerekir.

Teşhis tablosu

Teşhis tablosu: missing lifetime specifier, does not live long enough, cannot return reference to local variable, cannot return value referencing local variable
MesajKodAnlamıGenellikle çözüm
missing lifetime specifierE0106Çıktı referansının kaynağı belirsizLifetime parametresi, 'static veya sahipli tip
does not live long enoughE0597Değer, referansı kullanılırken bırakılmışDeğeri daha geniş kapsama taşımak
cannot return reference to local variableE0515Fonksiyon bitince yok olacak değere referansString, Vec gibi sahipli değer döndürmek
cannot return value referencing local variableE0515Döndürülen değer yerel veriye referans tutuyorSahiplenen tip, ör. into_iter()
temporary value dropped while borrowedE0716Geçici değer deyim sonunda bırakılıyorDeğeri let ile adlandırmak

Sık yapılan hatalar

  • E0597 veya E0515'i etiketle çözmeye çalışmak: Etiketler ömür uzatmaz. Değerin gerçekten nerede yok olduğuna bakın.
  • Her E0106'ya 'static yazmak: Bu, çağıranları yalnızca statik veriyle çalışmaya zorlar ve çoğu zaman bir sonraki satırda yeni bir hata doğurur.
  • Gereksiz yere referans tutan struct tasarlamak: Struct verinin sahibi olabiliyorsa String veya Vec<T> alanları kodu sadeleştirir. Referans tutan struct'lar, kısa ömürlü ayrıştırıcılar gibi gerçekten ödünç alma gereken durumlar içindir.
  • Tek parametreye etiket yazıp yeterli sanmak: Birden çok referans parametresi varsa çıktının hangisiyle ilişkili olduğu açıkça yazılmalıdır.
  • Çalışma anındaki değere güvenmek: Derleyici en_uzun örneğinde hangi dalın seçileceğine bakmaz; yalnızca imzadaki ilişkiyi denetler.

Sık Sorulan Sorular

Lifetime etiketi programın davranışını değiştirir mi?

Hayır. Kitaba göre etiketler hiçbir referansın ömrünü değiştirmez; yalnızca derleyiciye referanslar arasındaki ilişkiyi anlatır. Denetimi derleme sırasında ödünç alma denetleyicisi yapar ve kapsamları karşılaştırarak tüm ödünç almaların geçerli olup olmadığına karar verir.

'a adının bir anlamı var mı?

Hayır. Lifetime parametreleri kesme işaretiyle başlayan herhangi bir ad olabilir; kitap genellikle kısa ve küçük harfli adları, çoğunlukla 'a adını kullanır. Önemli olan, aynı adın hangi referansları birbirine bağladığıdır.

E0597 ile E0505 arasındaki fark nedir?

E0597, ödünç alınmış değerin kapsamdan çıkarak bırakılmasıdır. E0505 ise ödünç alınmış değerin başka bir yere taşınmasıdır. İkisinde de referans geçersiz kalacağı için derleyici durdurur; E0505 için E0382 ve move hataları yazısına bakın.

Derleyicinin önerisini doğrudan uygulamalı mıyım?

Öneriler çoğu zaman doğru yönü gösterir, ama birden fazla seçenek sunulduğunda tasarımınıza uygun olanı seçmelisiniz. Örneğin E0106 için hem 'static hem String önerildiğinde, değer çalışma anında üretiliyorsa doğru seçim String olur.

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 · Rust lifetime · Referans ve borrowing · E0382 move hataları · E0499 ve E0502 · String ve &str farkı