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:

  1. Her Rust değerinin bir sahibi vardır.
  2. Bir anda bir değerin yalnızca bir sahibi olabilir.
  3. 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. 

Ownership

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)

Borrowing

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:

  1. Bir anda bir değerin yalnızca bir değiştirilebilir referansı olabilir.
  2. 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

Borrowing-Ownership

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:

  1. Bir değerin sahipliğini taşıdıktan sonra ilk değişkeni kullanmaya çalışmak.
  2. Bir değerin değişmez bir referansını alıp değeri değiştirmeye çalışmak.
  3. 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

Karmaşık Sistemler

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

Rust Dokümantasyonu ve Kılavuzlar



Bu gibi daha fazla gönderi...

Rustda Güçlü ve Verimli Veri İşleme: Iteratorlarla Tanışın!

2024-04-15 | #rust

Rust dilinde iteratorler, koleksiyonlar üzerinde gezinmek ve üzerlerinde işlemler yapmak için kullanılan yapılar arasında yer alır. Iteratorlar, veri koleksiyonlarının elemanlarına erişmek ve bu elemanlar üzerinde işlemler yapabilmek için kullanılır. Rust’ın sahiplik ve ödünç verme kuralları ile entegre şekilde çalıştıkları için güvenli ve etkili bir kullanım sunarlar. Iterator Tanımlama ve Kullanma Iterator’ı kullanmanın temel yolu .iter(), .iter_mut(), ve .into_iter() metodlarıdır. Bu metodlar, koleksiyonun sahiplik durumuna göre değişir: .iter(): Koleksiyonun immutable referansları üzerinden iterasyon yapar.

Devamı 


Rust ta Crate ler: Modülerlik ve Yeniden Kullanımın Temeli

2024-04-03 | #crates #rust

Rust, modern ve güvenli bir programlama dili olarak, yazılım geliştirme dünyasında hızla popülerlik kazanıyor. Rust’ın en önemli özelliklerinden biri, modülerlik ve yeniden kullanılabilirlik üzerine kurulu olmasıdır. Bu da “crate” adı verilen bir kavramla sağlanır. Bu makalede, Rust’ta crate’lerin ne olduğunu, nasıl kullanıldığını ve neden önemli olduğunu detaylı bir şekilde ele alacağız. Crate Nedir? Basitçe söylemek gerekirse, bir crate, bir Rust projesinin temel yapı taşıdır. Bir veya birden fazla modülü içerebilen bir paket veya kütüphane olarak düşünülebilir.

Devamı 


Enum ve Pattern Matching: Rust'ta Güçlü Veri Yapıları

2023-08-25 | #rust #rust-enums #rust-patternmatching

Rust programlama dilinde enum ve pattern matching özellikleri, tip güvenliği ve kod okunabilirliği için oldukça önemli ve güçlüdür. enum (enumerasyonlar), farklı varyantları (variant) olan bir türü temsil eder. pattern matching ise, bu varyantları kolayca ve güvenli bir şekilde ele almanıza olanak tanır. Enum Nedir? enum keyword’ü ile bir enumerasyon oluşturabilirsiniz. Her bir “varyant” farklı bir olası değeri temsil eder. enum Renk { Kirmizi, Yesil, Mavi, } Bu örnekte Renk adlı bir enum tanımladık ve içerisine Kirmizi, Yesil, ve Mavi adlı varyantları ekledik.

Devamı 