Rust Programlama Dilindeki Ownership ve Borrowing: Hafıza Güvenliği ve Performansı Bir Arada Sağlamak
[en] Read in English 2023-06-28
Rust Programlama Dilinin Özgünlükleri
Rust, hafıza güvenliği, eşzamanlılık ve yüksek performansı bir arada sunmayı hedefleyen modern bir sistem programlama dilidir. Rust’ın en önemli özelliklerinden biri, dilin hafıza yönetim modelidir. Bu model, “ownership” (sahiplik) ve “borrowing” (ödünç alma) kavramlarına dayanır.
Ownership ve Borrowing Kavramlarının Önemi
Ownership ve Borrowing, Rust’ın hafıza yönetim modelinin temel taşlarıdır. Bu kavramlar, Rust’ın hafıza güvenliğini ve eşzamanlı çalışmayı sağlar. Bu makalede, bu iki kavramın ne olduğunu, nasıl çalıştığını ve Rust programlama dilinde nasıl kullanıldığını detaylı bir şekilde inceleyeceğiz.
Ownership (Sahiplik)
Sahiplik Kavramının Tanıtımı
Rust’ta, her değerin bir ‘sahibi’ vardır. Sahip olduğu değer, sahibi kapsamdan çıktığında otomatik olarak düşer. Bu, Rust’ın hafıza yönetimini nasıl gerçekleştirdiğinin temelidir.
Rust’ta Ownership’in İşleyişi
Rust’ta bir değişkenin sahipliği başka bir değişkene aktarıldığında, ilk değişken geçerliliğini yitirir. Bu, Rust’ın “double free” hafıza hatasını önlemesine yardımcı olur.
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
Bu kodu çalıştırdığınızda, bir hata alırsınız. Çünkü s1
‘in sahipliği s2
‘ye aktarıldı ve s1
geçerliliğini yitirdi.
Ownership Kuralları
Rust’ta sahiplikle ilgili üç ana kural vardır:
- Her Rust değerinin bir sahibi vardır.
- Bir anda bir değerin yalnızca bir sahibi olabilir.
- Değerin sahibi kapsamdan çıktığında, değer düşer.
Ownership ve Fonksiyonlar
Bir fonksiyona bir değer verdiğinizde, değerin sahipliği fonksiyona geçer. Fonksiyon sona erdiğinde, değer düşer.
fn main() {
let s = String::from("hello"); // s scope geliyor.
takes_ownership(s); // s değeri fonksiyona aktarılıyor......
// ... ve bu nedenle burada artık geçerli değil.
println!("{}", s); // Burada, s artık geçerli olmadığı için bir hata oluşturacak.
}
fn takes_ownership(some_string: String) { // some_string scope geliyor.
println!("{}", some_string);
} // Burada, some_string değişkeni kullanımdan kalkar ve drop çağrılır.
Yukarıdaki diyagramda, main()
fonksiyonu içinde tanımlanan s
değişkeninin değeri, takes_ownership()
fonksiyonuna taşınıyor. Bu taşıma işlemi sonucunda s
değişkeni artık main()
fonksiyonunda geçerli değildir. takes_ownership()
fonksiyonu sona erdiğinde, some_string
değişkeni kapsamdan çıkar ve hafızadan düşer.
Ownership ve Değişkenler
Rust’ta, bir değişkenin değeri başka bir değişkene atanırken, değer kopyalanmaz, sahiplik taşınır. Ancak, Copy
trait’ini implemente eden türler (örneğin, tüm integer türleri, boolean türü, karakter türü, float türleri ve tuple’lar) için, değerler otomatik olarak kopyalanır.
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // Bu, bir hata oluşturmayacak çünkü tamsayılar kopyalanabilir (Copy).
Ownership ve Diziler
Rust’ta, diziler ve stringler gibi bazı türler, heap üzerinde saklanır ve bu türlerin değerleri otomatik olarak kopyalanmaz. Bunun yerine, sahiplik taşınır.
let a = [1, 2, 3, 4, 5];
let b = a;
println!("a = {:?}, b = {:?}", a, b); // Bu bir hataya neden olacak çünkü diziler kopyalanabilir (Copy) değildir.
Ownership’in Hafıza Yönetimine Etkisi
Rust’ta, bir değerin sahibi kapsamdan çıktığında, değer otomatik olarak düşer ve hafıza geri alınır. Bu, Rust’ın hafıza yönetimini nasıl gerçekleştirdiğinin temelidir ve bu sayede Rust, hafıza sızıntılarını ve çift serbest bırakma hatalarını önler.
Borrowing (Ödünç Alma)
Borrowing Kavramının Tanıtımı
Borrowing, Rust’ta bir değerin sahipliğini taşımadan kullanılmasını sağlar. Bu, bir değeri okumak veya değiştirmek için kullanılabilir.
Rust’ta Borrowing’in İşleyişi
Bir değeri ödünç almak için, &
operatörünü kullanırız. Bu, bir referans oluşturur.
let s = String::from("hello");
let len = calculate_length(&s);
println!("The length of '{}' is {}.", s, len);
fn calculate_length(s: &String) -> usize {
s.len()
}
Bu kodda, calculate_length
fonksiyonuna s
stringinin bir referansını geçiriyoruz. Bu sayede, s
stringinin sahipliğini taşımadan, onun uzunluğunu hesaplayabiliyoruz.
Immutable Borrowing (Değişmez Ödünç Alma)
Yukarıdaki diyagramda, main()
fonksiyonu içinde tanımlanan s
stringinin bir referansı (&s
), calculate_length()
fonksiyonuna geçiriliyor. Bu sayede, s
stringinin sahipliği taşınmadan, onun uzunluğu hesaplanabiliyor.
Immutable Borrowing (Değişmez Ödünç Alma)
Bir değeri ödünç alırken, ödünç alınan değeri değiştiremezsiniz. Bu, Rust’ın hafıza güvenliğini sağlamasına yardımcı olur.
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world"); // Bu bir hataya neden olacak.
}
Bu kodu çalıştırdığınızda, bir hata alırsınız. Çünkü some_string
bir referans ve referanslar varsayılan olarak değişmezdir.
Mutable Borrowing (Değiştirilebilir Ödünç Alma)
Bir değeri değiştirebilmek için, mut
anahtar kelimesini kullanarak değiştirilebilir bir referans oluşturabiliriz.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Bu kod, s
stringine “, world” ekler ve bir hata oluşturmaz. Çünkü some_string
, s
stringinin değiştirilebilir bir referansıdır.
Borrowing Kuralları
Rust’ta borrowing ile ilgili iki ana kural vardır:
- Bir anda bir değerin yalnızca bir değiştirilebilir referansı olabilir.
- Bir değerin hem değiştirilebilir hem de değişmez referansları aynı anda olamaz.
Borrowing ve Fonksiyonlar
Bir fonksiyona bir değerin referansını geçirdiğinizde, fonksiyon değeri değiştiremez. Ancak, değiştirilebilir bir referans geçirirseniz, fonksiyon değeri değiştirebilir.
Borrowing ve Diziler
Rust’ta, diziler ve stringler gibi bazı türler, heap üzerinde saklanır ve bu türlerin değerleri otomatik olarak kopyalanmaz. Bunun yerine, değerler ödünç alınır.
let a = [1, 2, 3, 4, 5];
let b = &a;
println!("a = {:?}, b = {:?}", a, b); // Bu bir hataya neden olmayacak, çünkü b, a'ya bir referans.
Ownership ve Borrowing İlişkisi
Sahiplik ve Ödünç Almanın Birlikte Kullanımı
Rust’ta, bir değerin sahipliğini taşımadan kullanmak için borrowing kavramını kullanırız. Ancak, bir değeri değiştirmek istiyorsak, değerin sahipliğini taşım
Yukarıdaki diyagramda, main()
fonksiyonu içinde tanımlanan s
stringinin değiştirilebilir bir referansı (&mut s
), change()
fonksiyonuna geçiriliyor. Bu sayede, s
stringinin sahipliği taşınmadan, onun değeri değiştirilebiliyor.
İlişkilerinin Hafıza Güvenliğine Etkisi
Ownership ve borrowing kavramları, Rust’ın hafıza güvenliğini sağlar. Sahiplik, bir değerin yalnızca bir sahibi olduğunu ve sahibi kapsamdan çıktığında değerin düştüğünü garanti eder. Bu, hafıza sızıntılarını ve çift serbest bırakma hatalarını önler. Borrowing, bir değerin sahipliğini taşımadan kullanılmasını sağlar. Bu, hafıza güvenliğini sağlarken aynı zamanda verimliliği korur.
Yaygın Hatalar ve Çözümleri
Ownership ve Borrowing İle İlgili Yaygın Hatalar
Rust’ta, ownership ve borrowing ile ilgili en yaygın hatalar şunlardır:
- Bir değerin sahipliğini taşıdıktan sonra ilk değişkeni kullanmaya çalışmak.
- Bir değerin değişmez bir referansını alıp değeri değiştirmeye çalışmak.
- Bir değerin hem değiştirilebilir hem de değişmez referanslarını aynı anda oluşturmaya çalışmak.
Bu Hataların Nasıl Önlenebileceği
Bu hataları önlemek için, Rust’ın ownership ve borrowing kurallarını anlamak ve bu kurallara uymak önemlidir. Ayrıca, Rust’ın hata mesajlarını dikkatlice okumak ve anlamak da yardımcı olabilir. Rust’ın hata mesajları genellikle çok açıklayıcıdır ve neyin yanlış gittiğini ve nasıl düzeltilebileceğini belirtir.
Uygulama Örnekleri
Ownership ve Borrowing Kullanılan Basit Örnekler
Rust’ta, ownership ve borrowing kavramları genellikle birlikte kullanılır. İşte bir örnek:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // problem yok
let r2 = &s; // problem yok
let r3 = &mut s; // BÜYÜK PROBLEM :)
println!("{}, {}, and {}", r1, r2, r3);
}
Bu kodu çalıştırdığınızda, bir hata alırsınız. Çünkü s
stringinin hem değişmez hem de değiştirilebilir referansları aynı anda oluşturulmuştur.
Karmaşık Sistemler İçin Örnekler
Yukarıdaki diyagramda, main()
fonksiyonu içinde tanımlanan s
stringinin hem değişmez (&s
) hem de değiştirilebilir (&mut s
) referansları aynı anda oluşturulmuştur. Bu, Rust’ın borrowing kurallarına aykırıdır ve bir hata oluşturur.
Karmaşık Sistemler İçin Örnekler
Rust’ta, ownership ve borrowing kavramları, karmaşık sistemlerin hafıza yönetimini sağlamak için kullanılır. İşte bir örnek:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // problem yok
let r2 = &s; // problem yok
println!("{} and {}", r1, r2);
// Bu noktadan sonra r1 ve r2 kullanılmıyor.
let r3 = &mut s; // şimdi problem yok
println!("{}", r3);
}
Bu kodda, r1
ve r2
referansları println!
çağrıldıktan sonra kullanılmaz hale gelir. Bu nedenle, r3
referansını oluşturmakta bir sorun yoktur. Bu, Rust’ın hafıza güvenliğini sağlamasına yardımcı olur.
Sonuç
Rust’ın Hafıza Yönetimine Yaklaşımının Özeti
Rust, hafıza yönetimini, ownership ve borrowing kavramlarına dayanarak gerçekleştirir. Bu kavramlar, Rust’ın hafıza güvenliğini ve eşzamanlı çalışmayı sağlar. Rust, bu kavramları kullanarak, hafıza sızıntılarını ve çift serbest bırakma hatalarını önler.
Rust’ın Güvenlik ve Performansı Nasıl Sağladığı
Rust, hafıza güvenliğini ve performansı, ownership ve borrowing kavramlarına dayanarak sağlar. Bu kavramlar, Rust’ın hafıza yönetimini kontrol etmesine ve hafıza hatalarını önlemesine yardımcı olur. Ayrıca, Rust’ın bu kavramları kullanarak, hafıza yönetimini otomatik hale getirmesine gerek kalmaz. Bu, Rust’ın performansını artırır.
Diğer Programlama Dilleriyle Karşılaştırma
Rust’ın hafıza yönetim modeli, diğer dillerden farklıdır. Diğer diller genellikle otomatik hafıza yönetimine (örneğin, garbage collection) veya manuel hafıza yönetimine dayanır. Ancak, Rust, ownership ve borrowing kavramlarına dayanarak hafıza yönetimini gerçekleştirir. Bu, Rust’ın hafıza güvenliğini ve performansını sağlar.
Kaynaklar
Ek Bilgi İçin Okuma Önerileri
- The Rust Programming Language (Rust Programlama Dili)
- Rust by Example (Örneklerle Rust)
- Rustlings (Rust pratikleri)
Rust Dokümantasyonu ve Kılavuzlar
- Rust API Documentation (Rust API Dokümantasyonu)
- Rust Language Cheat Sheet (Rust Language Cheat Sheet)
- Rust Learning (Rust Learning)