Rust’ın bellek güvenliğindeki süper gücü, verileri sürekli kopyalamak veya çöp toplayıcı kullanmak değil; sahiplik ile borçlanmayı derleme aşamasında denetlemektir. Bir değeri başka bir fonksiyona gönderirken sahipliğini devretmek istemiyorsak & işaretiyle referans oluştururuz. Böylece fonksiyon veriyi geçici olarak ödünç alır; fakat onun kalıcı sahibi olmaz.
``
Sahiplik devri ile borçlanma arasındaki fark
Rust’ta her değerin tek bir sahibi vardır. Bir String başka bir değişkene doğrudan atanırsa çoğunlukla sahiplik taşınır. Eski değişken artık kullanılamaz. Referans kullanıldığında ise yalnızca verinin adresine güvenli bir erişim sağlanır.
fn uzunluk_hesapla(metin: &String) -> usize {
metin.len()
}
fn main() {
let mesaj = String::from("Merhaba Rust");
let uzunluk = uzunluk_hesapla(&mesaj);
println!("{} karakter: {}", uzunluk, mesaj);
}
Burada &mesaj, mesaj değişkeninin sahipliğini fonksiyona taşımaz. uzunluk_hesapla yalnızca salt okunur bir referans alır. Fonksiyon tamamlandığında borç sona erer ve asıl değer kullanılmaya devam eder.
| İşlem | Sözdizimi | Sahiplik taşınır mı? | Veri değiştirilebilir mi? |
|---|---|---|---|
| Değer aktarımı | islem(mesaj) |
Evet | Fonksiyona bağlı |
| Değişmez borç | islem(&mesaj) |
Hayır | Hayır |
| Değiştirilebilir borç | islem(&mut mesaj) |
Hayır | Evet |
Değişmez ve değiştirilebilir referanslar
Bir referans varsayılan olarak değişmezdir. Veriyi düzenlemek için hem değişkenin mut olarak tanımlanması hem de &mut referansı verilmesi gerekir.
fn unlem_ekle(metin: &mut String) {
metin.push('!');
}
fn main() {
let mut mesaj = String::from("Rust güvenlidir");
unlem_ekle(&mut mesaj);
println!("{}", mesaj);
}
Bu fonksiyon String değerini sahiplenmeden değiştirir. Kritik kural şudur: Belirli bir anda aynı veri için ya birden fazla değişmez referans ya da yalnızca bir değiştirilebilir referans bulunabilir.
Bu ilişkiyi basitleştirerek şöyle gösterebiliriz:
\[N_{mut} \leq 1\]Ayrıca değiştirilebilir bir referans etkinken değişmez referans sayısı sıfır olmalıdır:
\[N_{mut} = 1 \Rightarrow N_{shared} = 0\]| Geçerli durum | Değişmez referans | Değiştirilebilir referans | Sonuç |
|---|---|---|---|
| Çoklu okuma | Bir veya daha fazla | 0 | Güvenli |
| Tek yazıcı | 0 | 1 | Güvenli |
| Okuma ve yazma | Bir veya daha fazla | 1 | Reddedilir |
| Çoklu yazıcı | 0 | İki veya daha fazla | Reddedilir |
Neden yalnızca tek değiştirilebilir referans var?
İki kod parçasının aynı veriyi eş zamanlı değiştirebildiğini düşünelim. Biri dizinin kapasitesini büyütüp bellekte başka bir konuma taşırken diğeri eski adresi kullanabilir. Bu durum veri yarışı, geçersiz işaretçi ve beklenmeyen sonuçlar üretebilir. Borrow checker bu olasılığı program çalışmadan engeller.
let mut sayilar = vec![1, 2, 3];
let ilk = &mut sayilar;
// let ikinci = &mut sayilar; // Derleme hatası
ilk.push(4);
ilk değiştirilebilir borcu aktifken ikinci bir başka yazma yetkisi oluşturmaya çalışır. Rust bunu reddederek “tek veri, tek aktif yazıcı” ilkesini uygular.
Modern Rust, Non-Lexical Lifetimes sayesinde referansın ömrünü yalnızca süslü parantezlere göre değil, son kullanıldığı noktaya göre değerlendirir:
let mut puan = 10;
let gorunum = &puan;
println!("{}", gorunum); // Değişmez borcun son kullanımı
let duzenleyici = &mut puan;
*duzenleyici += 5;
İki referans aynı blokta görünse de etkin kullanım süreleri çakışmadığından kod güvenlidir. Özetle &T paylaşılabilen okuma izni, &mut T ise özel yazma iznidir. Rust’ın ampersandı sadece bir adres operatörü değil, derleyici tarafından denetlenen geçici bir erişim sözleşmesidir.
Yorumlar