-
Sadrazamv4
instagram:sadrazam.spy Tg:Sadrazamv4
🚀 BAŞTAN SONA RUST DİLİ BÖLÜM-18: Makrolar (Macros) ve Meta-Programming 🧙♂️
Selamlar! Serimizin 18. bölümüne hoş geldiniz.
Önceki bölümde `tokio` runtime ile asenkron programlama, `async/await` mimarisi ve eşzamanlı TCP sunucu geliştirmeyi incelemiştik. Bu bölümde ise Rust’ın en güçlü ve büyüleyici özelliklerinden biri olan **Makro Sistemini (Macros)** ve **Meta-Programming** konusunu ele alacağız.
Makrolar, basitçe "kod yazan kodlar" olarak tanımlanabilir. Rust'ta makroları bildiğimiz standart fonksiyonlardan ayıran en büyük farklar şunlardır:
• **Değişken Sayıda Parametre (Variadic Arguments):** `println!` veya `vec!` gibi makrolara istediğiniz sayıda parametre geçebilirsiniz.
• **Derleme Zamanı Çalışma (Compile-Time Execution):** Makrolar kod derlenmeden önce genişletilir (macro expansion), dolayısıyla çalışma zamanında (runtime) ek hiçbir maliyet yaratmazlar.
Rust'ta iki ana makro türü vardır:
1. **Declarative Macros (Bildirimsel Makrolar):** `macro_rules!` ile yazılan desen eşlemeli yapılar.
2. **Procedural Macros (Prosedürel Makrolar):** Kod üzerinde AST (Abstract Syntax Tree) düzeyinde işlem yapan yapılar.
---
📌 1. Declarative Macros (`macro_rules!`) 🎯
Declarative Makrolar, Rust'ta en sık kullanılan ve `match` ifadesine benzer bir desen eşleme (pattern matching) mantığıyla çalışan yapılardır.
Basit bir vektör oluşturma makrosu mantığını inceleyelim:
macro_rules! benim_vec {
( $( $x:expr ),* ) => {
{
let mut gecici_vec = Vec::new();
$(
gecici_vec.push($x);
)*
gecici_vec
}
};
}
fn main() {
let v = benim_vec![1, 2, 3, 4];
println!("Oluşturulan Vektör: {:?}", v);
}
#### Makro Dizilim Mantığı ve Tasarımcılar (Designators):
Yukarıdaki makroda yer alan işaretlerin anlamı şudur:
• `$x:expr`: `x` adında bir ifade (expression) yakalar.
• `$( ... ),*`: Virgülle ayrılmış sıfır veya daha fazla öğeyi tekrarlayarak yakalar (`*` = 0 veya daha fazla, `+` = 1 veya daha fazla).
• `$x:expr` ifadesi her tekrarda bir kez genişletilir ve `gecici_vec.push(...)` komutuna dönüşür.
Sık kullanılan tasarımcı tipleri (Designators):
- `expr`: Herhangi bir ifade (örn: `1 + 2`, `"metin"`, `x.foo()`)
- `ident`: Tanımlayıcı / Değişken veya fonksiyon adı (örn: `sayi`, `kullanici_adi`)
- `ty`: Veri tipi (örn: `i32`, `String`, `Vec<u8>`)
- `pat`: Desen / Pattern (örn: `Some(x)`, `_`)
- `stmt`: Bir deyim / Statement (örn: `let x = 5;`)
---
⚙️ 2. Gelişmiş Bildirimsel Makro Örneği 🛠️
Aşağıdaki örnekte birden fazla deseni eşleyebilen esnek bir harita (HashMap) makrosu oluşturalım:
use std::collections::HashMap;
macro_rules! hashmap {
( $( $key:expr => $val:expr ),* $(,)? ) => {
{
let mut map = HashMap::new();
$(
map.insert($key, $val);
)*
map
}
};
}
fn main() {
let harita = hashmap! {
"Mavi" => 10,
"Kırmızı" => 20,
"Yeşil" => 30, // Sondaki virgül de '$(,)?' sayesinde desteklenir
};
println!("{:?}", harita);
}
---
🧬 3. Procedural Macros (Prosedürel Makrolar) 🏗️
Prosedürel makrolar bir işlev gibi çalışır: Rust kodunu girdi (TokenStream) olarak alır, derleme zamanında üzerinde matematiksel/mantıksal işlemler yapar ve geriye yeni bir Rust kodu (TokenStream) üretir.
Procedural Makrolar kendi özel kütüphane projelerinde (`proc-macro = true` tanımlı `Cargo.toml`) yazılmalıdır.
3 farklı türe ayrılırlar:
1. **Custom Derive (`#[derive(MyTrait)]`):** Struct veya Enum'lara otomatik Trait implementasyonu ekler.
2. **Attribute-like (`#[route(GET, "/")]`):** Kod ögelerine özel nitelik/annotation eklememizi sağlar.
3. **Function-like (`sql!("SELECT * FROM users")`):** Fonksiyon çağrısı şeklinde çalışan gelişmiş makrolardır.
---
🛠️ 4. Custom Derive Makro Yapısı 🎨
Gerçek hayatta en sık karşılaştığımız `#[derive(Serialize, Deserialize)]` veya `#[derive(Debug)]` gibi yapılar birer Custom Derive makrosudur.
Prosedürel bir makronun temel yapısı şu şekildedir:
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(Yazdirilabilir)]
pub fn yazdirilabilir_derive(input: TokenStream) -> TokenStream {
// 1. Gelen AST (Abstract Syntax Tree) yapısını ayrıştır
let ast = syn:
arse(input).unwrap();
// 2. Trait uygulamasını oluştur
impl_yazdirilabilir(&ast)
}
fn impl_yazdirilabilir(ast: &syn:
eriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl Yazdirilabilir for #name {
fn yazdir(&self) {
println!("Bu nesne bir '{}' yapısıdır!", stringify!(#name));
}
}
};
gen.into()
}
Kullanımı:
#[derive(Yazdirilabilir)]
struct Kullanici {
ad: String,
}
fn main() {
let k = Kullanici { ad: String::from("Ahmet") };
k.yazdir(); // Makro tarafından otomatik türetilen metot!
}
---
⚖️ 5. Makrolar vs Fonksiyonlar: Ne Zaman Hangisi? 📊
| Özellik | Fonksiyonlar (`fn`) | Makrolar (`macro_rules!`) |
| :--- | :--- | :--- |
| Değişken Parametre | Desteklemez (Sabit sayıda) | Esnek (Değişken sayıda) |
| Derleme Süresi | Hızlı | Kod üretimi nedeniyle bir miktar yavaşlatabilir |
| Çalışma Zamanı Maliyeti | Standart çağrı maliyeti | Sıfır (Zero Runtime Cost) |
| Hata Okunabilirliği | Net ve açık hata mesajları | Karmaşık makro hata mesajları oluşabilir |
| Tip Güvenliği | Çağrı anında tip kontrolü | Derleme genişletme anında kontrol |
> **Genel İlke:** Eğer yapmak istediğiniz işlem normal bir fonksiyon veya Trait ile çözülebiliyorsa öncelikle fonksiyon tercih edilmelidir. Makrolar yalnızca değişken sayıda parametre, derleme zamanı kod üretimi veya Domain-Specific Language (DSL) yazımı gerektiğinde kullanılmalıdır.
---
📝 Bölüm Özeti ve Gelecek Bölüm 🔮
Bu bölümde Declarative Makro yapısını (`macro_rules!`), desen eşleme ve tasarımcıları (designators), Procedural Makroların mantığını ve Custom Derive mimarisini öğrendik.
👉 Bölüm 19'da Bizi Ne Bekliyor?
• Sistem Seviyesi ve Düşük Seviyeli Programlama: **Unsafe Rust ve FFI (Foreign Function Interface)** ☣️
• Bare-metal / Low-level konseptler
• Raw Pointers (`*const T`, `*mut T`)
• C küt
üphaneleri ile entegrasyon (`extern "C"`) ve bellek ihlali yönetimi
Sorularınızı konu altından iletebilirsiniz. Serimizin bir sonraki bölümünde görüşmek üzere! 🦀✨
Selamlar! Serimizin 18. bölümüne hoş geldiniz.
Önceki bölümde `tokio` runtime ile asenkron programlama, `async/await` mimarisi ve eşzamanlı TCP sunucu geliştirmeyi incelemiştik. Bu bölümde ise Rust’ın en güçlü ve büyüleyici özelliklerinden biri olan **Makro Sistemini (Macros)** ve **Meta-Programming** konusunu ele alacağız.
Makrolar, basitçe "kod yazan kodlar" olarak tanımlanabilir. Rust'ta makroları bildiğimiz standart fonksiyonlardan ayıran en büyük farklar şunlardır:
• **Değişken Sayıda Parametre (Variadic Arguments):** `println!` veya `vec!` gibi makrolara istediğiniz sayıda parametre geçebilirsiniz.
• **Derleme Zamanı Çalışma (Compile-Time Execution):** Makrolar kod derlenmeden önce genişletilir (macro expansion), dolayısıyla çalışma zamanında (runtime) ek hiçbir maliyet yaratmazlar.
Rust'ta iki ana makro türü vardır:
1. **Declarative Macros (Bildirimsel Makrolar):** `macro_rules!` ile yazılan desen eşlemeli yapılar.
2. **Procedural Macros (Prosedürel Makrolar):** Kod üzerinde AST (Abstract Syntax Tree) düzeyinde işlem yapan yapılar.
---
📌 1. Declarative Macros (`macro_rules!`) 🎯
Declarative Makrolar, Rust'ta en sık kullanılan ve `match` ifadesine benzer bir desen eşleme (pattern matching) mantığıyla çalışan yapılardır.
Basit bir vektör oluşturma makrosu mantığını inceleyelim:
macro_rules! benim_vec {
( $( $x:expr ),* ) => {
{
let mut gecici_vec = Vec::new();
$(
gecici_vec.push($x);
)*
gecici_vec
}
};
}
fn main() {
let v = benim_vec![1, 2, 3, 4];
println!("Oluşturulan Vektör: {:?}", v);
}
#### Makro Dizilim Mantığı ve Tasarımcılar (Designators):
Yukarıdaki makroda yer alan işaretlerin anlamı şudur:
• `$x:expr`: `x` adında bir ifade (expression) yakalar.
• `$( ... ),*`: Virgülle ayrılmış sıfır veya daha fazla öğeyi tekrarlayarak yakalar (`*` = 0 veya daha fazla, `+` = 1 veya daha fazla).
• `$x:expr` ifadesi her tekrarda bir kez genişletilir ve `gecici_vec.push(...)` komutuna dönüşür.
Sık kullanılan tasarımcı tipleri (Designators):
- `expr`: Herhangi bir ifade (örn: `1 + 2`, `"metin"`, `x.foo()`)
- `ident`: Tanımlayıcı / Değişken veya fonksiyon adı (örn: `sayi`, `kullanici_adi`)
- `ty`: Veri tipi (örn: `i32`, `String`, `Vec<u8>`)
- `pat`: Desen / Pattern (örn: `Some(x)`, `_`)
- `stmt`: Bir deyim / Statement (örn: `let x = 5;`)
---
⚙️ 2. Gelişmiş Bildirimsel Makro Örneği 🛠️
Aşağıdaki örnekte birden fazla deseni eşleyebilen esnek bir harita (HashMap) makrosu oluşturalım:
use std::collections::HashMap;
macro_rules! hashmap {
( $( $key:expr => $val:expr ),* $(,)? ) => {
{
let mut map = HashMap::new();
$(
map.insert($key, $val);
)*
map
}
};
}
fn main() {
let harita = hashmap! {
"Mavi" => 10,
"Kırmızı" => 20,
"Yeşil" => 30, // Sondaki virgül de '$(,)?' sayesinde desteklenir
};
println!("{:?}", harita);
}
---
🧬 3. Procedural Macros (Prosedürel Makrolar) 🏗️
Prosedürel makrolar bir işlev gibi çalışır: Rust kodunu girdi (TokenStream) olarak alır, derleme zamanında üzerinde matematiksel/mantıksal işlemler yapar ve geriye yeni bir Rust kodu (TokenStream) üretir.
Procedural Makrolar kendi özel kütüphane projelerinde (`proc-macro = true` tanımlı `Cargo.toml`) yazılmalıdır.
3 farklı türe ayrılırlar:
1. **Custom Derive (`#[derive(MyTrait)]`):** Struct veya Enum'lara otomatik Trait implementasyonu ekler.
2. **Attribute-like (`#[route(GET, "/")]`):** Kod ögelerine özel nitelik/annotation eklememizi sağlar.
3. **Function-like (`sql!("SELECT * FROM users")`):** Fonksiyon çağrısı şeklinde çalışan gelişmiş makrolardır.
---
🛠️ 4. Custom Derive Makro Yapısı 🎨
Gerçek hayatta en sık karşılaştığımız `#[derive(Serialize, Deserialize)]` veya `#[derive(Debug)]` gibi yapılar birer Custom Derive makrosudur.
Prosedürel bir makronun temel yapısı şu şekildedir:
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(Yazdirilabilir)]
pub fn yazdirilabilir_derive(input: TokenStream) -> TokenStream {
// 1. Gelen AST (Abstract Syntax Tree) yapısını ayrıştır
let ast = syn:
// 2. Trait uygulamasını oluştur
impl_yazdirilabilir(&ast)
}
fn impl_yazdirilabilir(ast: &syn:
let name = &ast.ident;
let gen = quote! {
impl Yazdirilabilir for #name {
fn yazdir(&self) {
println!("Bu nesne bir '{}' yapısıdır!", stringify!(#name));
}
}
};
gen.into()
}
Kullanımı:
#[derive(Yazdirilabilir)]
struct Kullanici {
ad: String,
}
fn main() {
let k = Kullanici { ad: String::from("Ahmet") };
k.yazdir(); // Makro tarafından otomatik türetilen metot!
}
---
⚖️ 5. Makrolar vs Fonksiyonlar: Ne Zaman Hangisi? 📊
| Özellik | Fonksiyonlar (`fn`) | Makrolar (`macro_rules!`) |
| :--- | :--- | :--- |
| Değişken Parametre | Desteklemez (Sabit sayıda) | Esnek (Değişken sayıda) |
| Derleme Süresi | Hızlı | Kod üretimi nedeniyle bir miktar yavaşlatabilir |
| Çalışma Zamanı Maliyeti | Standart çağrı maliyeti | Sıfır (Zero Runtime Cost) |
| Hata Okunabilirliği | Net ve açık hata mesajları | Karmaşık makro hata mesajları oluşabilir |
| Tip Güvenliği | Çağrı anında tip kontrolü | Derleme genişletme anında kontrol |
> **Genel İlke:** Eğer yapmak istediğiniz işlem normal bir fonksiyon veya Trait ile çözülebiliyorsa öncelikle fonksiyon tercih edilmelidir. Makrolar yalnızca değişken sayıda parametre, derleme zamanı kod üretimi veya Domain-Specific Language (DSL) yazımı gerektiğinde kullanılmalıdır.
---
📝 Bölüm Özeti ve Gelecek Bölüm 🔮
Bu bölümde Declarative Makro yapısını (`macro_rules!`), desen eşleme ve tasarımcıları (designators), Procedural Makroların mantığını ve Custom Derive mimarisini öğrendik.
👉 Bölüm 19'da Bizi Ne Bekliyor?
• Sistem Seviyesi ve Düşük Seviyeli Programlama: **Unsafe Rust ve FFI (Foreign Function Interface)** ☣️
• Bare-metal / Low-level konseptler
• Raw Pointers (`*const T`, `*mut T`)
• C küt
üphaneleri ile entegrasyon (`extern "C"`) ve bellek ihlali yönetimi
Sorularınızı konu altından iletebilirsiniz. Serimizin bir sonraki bölümünde görüşmek üzere! 🦀✨
🔒 Bu içeriği görmek için giriş yapın