🚀 BAŞTAN SONA RUST DİLİ BÖLÜM-11: Generics (Jenerikler) ve Kod Esnekliği 🧬

Konu Özeti

Rust programlama dilinde jenerikler (generics) konusu ele alınıyor. Kod tekrarını önlemek için fonksiyon, struct ve enum yapılarında tür parametreleri kullanımı örneklerle açıklanıyor. Ayrıca, jenerik yapılara metot ekleme, belirli türler için özel implementasyonlar ve Rust'ın sıfır maliyetli soyutlama sağlayan monomorphization mekanizması anlatılıyor.

🚀 BAŞTAN SONA RUST DİLİ BÖLÜM-11: Generics (Jenerikler) ve Kod Esnekliği 🧬

Selamlar! Serimizin 11. bölümüne hoş geldiniz.

Önceki bölümde projemizi modüllere nasıl ayıracağımızı, `pub` ile erişim kontrollerini ve `use` kullanımını öğrenmiştik. Bu bölümde ise kod tekrarını önleyen, türden bağımsız esnek ve güvenli yapılar oluşturmamızı sağlayan **Generics (Jenerikler)** konusunu inceleyeceğiz.

Generics, türleri (types) soyutlayarak tek bir fonksiyon, struct veya enum tanımı ile farklı veri tipleriyle çalışmamıza imkan tanır.

---

📌 1. Neden Generics Kullanırız? 🤔

Diyelim ki bir dizideki en büyük elemanı bulan bir fonksiyon yazmak istiyoruz.

Önce `i32` için yazalım:

fn en_buyuk_i32(liste: &[i32]) -> &i32 {
let mut en_buyuk = &liste[0];
for eleman in liste {
if eleman > en_buyuk {
en_buyuk = eleman;
}
}
en_buyuk
}

Peki aynı işlemi `char` veya `f64` tipleri için de yapmamız gerekirse ne olacak? Fonksiyonu kopyalayıp sadece veri tiplerini değiştirmek ciddi bir kod tekrarına (code duplication) yol açar.

İşte tam bu noktada **Generics** devreye girer!

---

⚙️ 2. Fonksiyonlarda Generics Kullanımı 🛠️

Jenerik tür parametreleri genellikle tek harfli büyük harflerle temsil edilir (Standart olarak `T` kullanılır - "Type" anlamında).

Açısal parantezler `<T>` kullanılarak fonksiyona tanımlanır:

// T tipinde bir dilim (slice) alır ve T tipinde referans döner
fn en_buyuk<T: std::cmp::partialOrd>(liste: &[T]) -> &T {
let mut en_buyuk = &liste[0];
for eleman in liste {
if eleman > en_buyuk { // Karşılaştırma yapabilmek için PartialOrd kısıtlaması şarttır
en_buyuk = eleman;
}
}
en_buyuk
}

fn main() {
let sayi_listesi = vec![34, 50, 25, 100, 65];
let sonuc = en_buyuk(&sayi_listesi);
println!("En büyük sayı: {}", sonuc);

let karakter_listesi = vec!['y', 'm', 'a', 'q'];
let sonuc = en_buyuk(&karakter_listesi);
println!("En büyük karakter: {}", sonuc);
}

> **Not:** `T: std::cmp::partialOrd` ifadesi bir **Trait Bound**'dur. Derleyiciye "Gelecek `T` tipi birbiriyle karşılaştırılabilir (`>`) bir tip olmalıdır" mesajı verir. Trait konusunu Bölüm 12'de detaylıca işleyeceğiz!

---

🏗️ 3. Struct Yapılarında Generics 🧱

Struct tanımlarken de bir veya birden fazla jenerik tip parametresi kullanabiliriz.

### Tek Tip Parametreli Struct:
Tüm jenerik alanlar aynı tipten olmak zorundadır.

struct Nokta<T> {
x: T,
y: T,
}

fn main() {
let tamsayi = Nokta { x: 5, y: 10 }; // T -> i32
let ondalik = Nokta { x: 1.0, y: 4.0 }; // T -> f64

// let hatali = Nokta { x: 5, y: 4.0 }; // HATA! x i32 iken y f64 olamaz.
}

### Birden Fazla Tip Parametreli Struct:
Farklı tipleri desteklemek istiyorsak birden fazla parametre tanımlarız (`<T, U>`):

struct NoktaEsnek<T, U> {
x: T,
y: U,
}

fn main() {
let karma = NoktaEsnek { x: 5, y: 4.0 }; // T -> i32, U -> f64. Sorunsuz çalışır!
}

---

🎨 4. Enum Yapılarında Generics 🎭

Rust'ın standart kütüphanesindeki en temel yapılar Generics üzerine kurulmuştur. Daha önce gördüğümüz `Option<T>` ve `Result<T, E>` buna en güzel örnektir:

// Tek jenerik parametreli Enum
enum Option<T> {
Some(T),
None,
}

// İki jenerik parametreli Enum
enum Result<T, E> {
Ok(T),
Err(E),
}

Bu sayede `Option`, bir tam sayı (`Option<i32>`), metin (`Option<String>`) veya kendi yazdığımız bir Struct (`Option<Kullanici>`) tutabilir.

---

⚙️ 5. `impl` Bloklarında Generics ve Metot Tanımları 🛠️

Jenerik bir Struct için metot yazarken `impl` kelimesinden hemen sonra `<T>` parametresini bildirmemiz gerekir.

struct Nokta<T> {
x: T,
y: T,
}

// Jenerik metot bloğu
impl<T> Nokta<T> {
fn x(&self) -> &T {
&self.x
}
}

// Sadece belirli bir tip için geçerli olan metot bloğu (Concrete implementation)
impl Nokta<f32> {
fn orijine_uzaklik(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

fn main() {
let p1 = Nokta { x: 5, y: 10 };
println!("p1.x = {}", p1.x());

let p2 = Nokta { x: 3.0_f32, y: 4.0_f32 };
println!("Orijine uzaklık: {}", p2.orijine_uzaklik());

// p1.orijine_uzaklik(); // HATA! p1 (i32) olduğu için bu metoda sahip değildir.
}

---

⚡ 6. Generics Performans Etkisi: Monomorphization 🚀

C++ veya Java gibi dillerdeki jenerik yapılarda "çalışma zamanı maliyeti" (runtime overhead) endişesi yaşanabilir.

Rust'ta ise **Generics kullanmanın ÇALIŞMA ZAMANI MALİYETİ SIFIRDIR (Zero-Cost Abstraction)!**

Derleyici, derleme aşamasında (compile-time) jenerik kodları inceler ve kullanılan somut tiplere göre kodu çoğaltır. Bu işleme **Monomorphization** denir.

Örnek olarak, siz şunu yazarsınız:
let int_option = Some(5);
let float_option = Some(5.0);

Derleyici bunu şu şekilde iki ayrı somut tipe dönüştürür:
enum Option_i32 {
Some(i32),
None,
}

enum Option_f64 {
Some(f64),
None,
}

Sonuç olarak: Çalışma zamanında hiçbir arama/tip kontrolü yapılmaz, doğrudan somut tiple çalışıldığı için kod C hızında çalışır!

---

📝 Bölüm Özeti ve Gelecek Bölüm 🔮

Bu bölümde Generics kavramını, fonksiyon, struct ve enum'larda esnek tür yönetimini, özel `impl` bloklarını ve Rust'ın Monomorphization mekanizmasını gördük.

👉 **Bölüm 12'de Bizi Ne Bekliyor?**
• Rust'ın Arayüz/İnterfase Yapısı: **Traits (Nitelikler)**
• Trait tanımlama ve bir Struct'a `impl Trait
for Struct` ile uygulama
• **Trait Bounds** (`where` cümlesi)
• `impl Trait` dönüş tipleri

Sorularınızı konu altından iletebilirsiniz. Bir sonraki bölümde görüşmek üzere! 🦀✨

🔒 Bu içeriği görmek için giriş yapın

 
Yanıt yazmak için giriş yapmalısınız
Forum özelliklerini kullanmak ve Level 2 üyelik satın almak için hesabınıza giriş yapın.
136,860Threads
3,308,858Messages
328,695Members
EONLatest member
Top Bottom