00 C #define'dan Rust makrolarına
C önişlemcisi metin üzerinde çalışır: derleyici makroyu görmez, sadece sonucunu görür. Rust makroları ise token akışı (token stream) ve AST düzeyinde, derleyicinin içinde çalışır. Bu tek fark, C'de bildiğin her makro tuzağını ortadan kaldırır.
#define bir metin değiştirme motorudur. Önişlemci tipleri, kapsamı, operatör önceliğini bilmez; sadece karakter dizilerini başka karakter dizileriyle değiştirir. Bundan dolayı klasik C makro tuzakları doğar: parantezlenmemiş argümanlar yüzünden bozulan operatör önceliği, argümanın iki kez yazılmasından doğan çift değerlendirme (double evaluation), ve makro içindeki geçici isimlerin çağıran kapsama sızması.
// Üç tuzak birden: öncelik, çift değerlendirme, tip yok
#define SQUARE(x) x * x // SQUARE(a+b) -> a + b*a + b (öncelik bozuk)
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int main(void) {
int i = 1;
// i++ iki kez değerlendirilir: tanımsız davranışa yakın
int m = MAX(i++, 5); // i beklenmedik biçimde artar
return m;
}Rust makroları bunların hiçbirini yaşamaz. Bir Rust makrosu lexer'dan geçmiş token ağacı (token tree) üzerinde çalışır, çıktısı tekrar derleyicinin sözdizimi denetiminden geçer. Argümanlar parantez/öncelik açısından bütünlüklü birer ifade olarak taşınır — $x:expr ile yakaladığın şey bir karakter dizisi değil, ayrıştırılmış bir ifadedir. Üstelik makrolar hijyeniktir (hygiene): makronun içinde tanımladığın bir let değişkeni, çağıran kodun değişkenleriyle asla çakışmaz.
C #define Rust makro ───────────── ───────────── ham metin → token tree (lexed) önişlemci → derleyici içi genişletme kapsam sızar → hijyenik (kapsam korunur) tip/AST yok → :expr :ty :ident (AST farkında) çıktı denetsiz → çıktı yeniden ayrıştırılır
Peki makro her zaman doğru araç mı? Hayır. Çalışma zamanı değeri alıp değer döndüren sıradan işler için fonksiyon, tip üzerinde soyutlama için generic tercih edilir — bunlar daha iyi hata mesajı, daha iyi IDE desteği verir. Makroya yalnızca dilin izin vermediğini yapman gerektiğinde uzan: değişken sayıda argüman (variadic), derleme zamanında format/tip denetimi, ya da boilerplate kod üretimi.
Pratik kural: "Bunu bir fonksiyon ya da generic ile yapabiliyor muyum?" sorusunun cevabı evet ise makro yazma. Makro, dilin sözdizimini genişletmek için son çaredir — daha güçlü ama hata ayıklaması ve okuması daha zordur.
Bu bölümde
- C önişlemcisi metin değiştirir; tip, kapsam, öncelik bilmez
- Klasik tuzaklar: öncelik bozulması, çift değerlendirme, kapsam sızıntısı
- Rust makroları token/AST düzeyinde, hijyenik ve denetimli çalışır
- Fonksiyon/generic yetmediğinde (variadic, kod üretimi) makroya geç
01 macro_rules! temel
macro_rules! bildirimsel (declarative) makro tanımlar: bir dizi kuraldan oluşur, her kural bir matcher (eşleştirici) ile bir transcriber (üretici) çiftidir. Çağrı bir desene uyarsa, ona karşılık gelen gövdeye genişler.
Yapı şudur: (desen) => { üretilecek_kod };. Sol taraf çağrıyı eşler, ok => ile ayrılır, sağ taraf genişletilecek koddur. Bu, C'deki #define ile yüzeysel benzer görünür ama tamamen farklıdır: desen tarafı tip-farkında token kalıplarıdır, üretici tarafı yeniden derlenecek gerçek Rust kodudur.
// Tek kurallı en basit makro. $x bir ifadeyi yakalar.
macro_rules! kare {
// matcher => transcriber
($x:expr) => { $x * $x };
}
fn main() {
// C SQUARE'in aksine: $x bütün bir ifade olarak taşınır
let a = kare!(3); // 9
let b = kare!(2 + 1); // 9, parantez sorunu YOK
println!("{a} {b}");
}Önemli ayrım: kare!(2 + 1) çağrısında $x tek bir ifade düğümü olarak (2 + 1) şeklinde yakalanır. Genişleme aslında (2 + 1) * (2 + 1) gibi davranır; C'deki 2 + 1 * 2 + 1 öncelik kazası olmaz. Çünkü :expr ile yakalanan parça artık bölünmez bir AST düğümüdür.
Makro derleme zamanında genişler. kare!(3)'ün yerine derleyici doğrudan 3 * 3 kodunu koyar; çalışma zamanında "makro" diye bir varlık kalmaz. Bu yüzden makro çağrısının çağrı maliyeti sıfırdır — fonksiyon çağrısı değil, yerinde açılan koddur.
Makro çağrısını kare!(...), kare![...] veya kare!{...} biçiminde yapabilirsin; üç ayraç da geçerlidir. Gelenek: ifade benzeri için (), koleksiyon için [] (örn. vec![]), blok benzeri için {}.
Bu bölümde
- macro_rules! kuralları matcher => transcriber çiftleridir
- :expr ile yakalanan argüman bölünmez bir AST düğümüdür (öncelik güvenli)
- Makro derleme zamanında genişler; çalışma zamanı maliyeti yoktur
- Çağrıda ( ), [ ], { } ayraçlarının üçü de geçerlidir
02 Fragment belirteçleri
Matcher içindeki $ad:tür ifadesinde tür, bir fragment specifier'dır (fragment belirteci). Yakaladığın token grubunun hangi gramer kuralına uyması gerektiğini belirtir. Bu, C makrolarında imkânsız olan tip-farkındalığın kalbidir.
Her belirteç farklı bir gramer parçasını eşler. Yanlış belirteç seçmek derleme hatası verir — bu iyidir, çünkü hata makro tanımında yakalanır, çağrının derinlerinde değil. En sık kullanılan belirteçler:
| Belirteç | Eşlediği | Örnek |
|---|---|---|
:expr | bir ifade | 2 + 1, foo() |
:ident | tanımlayıcı / anahtar sözcük | x, main |
:ty | bir tip | u32, Vec<T> |
:tt | tek token ağacı (en esnek) | +, (a b) |
:block | küme parantezli blok | { let x = 1; x } |
:pat | bir desen (pattern) | Some(x), _ |
:literal | literal değer | 42, "sa" |
:path | nitelikli yol | std::mem::swap |
// :ident ile değişken adı, :ty ile tip, :expr ile başlangıç üretiyoruz
macro_rules! degisken_yarat {
($ad:ident : $tip:ty = $ilk:expr) => {
let $ad: $tip = $ilk;
};
}
fn main() {
degisken_yarat!(sayac : u32 = 0); // let sayac: u32 = 0;
degisken_yarat!(isim : &str = "x3");
println!("{sayac} {isim}");
}:tt (token tree) en esnek belirteçtir ama en az denetimlidir — neredeyse her şeyi yakalar. Karmaşık makrolarda recursive desenler için gerekir, fakat mümkünse :expr, :ty gibi daha dar belirteçleri tercih et: daha iyi hata mesajı üretirler.
Belirteçlerin ardından gelen token'a kısıtlama vardır (follow-set kuralı): örneğin :expr'den sonra yalnızca =>, , veya ; gelebilir. Derleyici bunu zorlar; gelecekte makronun anlamı kaymasın diye konmuş bir güvenliktir.
Bu bölümde
- $ad:belirteç yakalanan parçanın gramer kuralını sabitler
- :expr :ident :ty :tt :block :pat :literal :path en sık kullanılanlar
- :tt en esnek ama en denetimsizdir; mümkünse dar belirteç seç
- Yanlış belirteç hatayı çağrıda değil, makro tanımında yakalar
03 Tekrar (repetition)
C'de değişken argümanlı makro (__VA_ARGS__) sefil bir deneyimdir. Rust'ta $(...) tekrar sözdizimi ile değişken sayıda argümanı temiz biçimde eşler ve aynı şekilde geri yayarsın (expand).
Tekrar üç parçadan oluşur: tekrarlanan desen $( ... ), isteğe bağlı bir ayraç (separator), ve bir tekrar operatörü. Operatörler C regex'ine benzer:
| Sözdizimi | Anlamı |
|---|---|
$(...),* | virgülle ayrılmış, sıfır veya daha fazla |
$(...)* | ayraçsız, sıfır veya daha fazla |
$(...)+ | bir veya daha fazla (en az bir) |
$(...)? | sıfır veya bir (opsiyonel) |
Püf nokta: matcher'da $(...),* ile yakaladığın şeyi, transcriber'da yine $(...)* ile geri yayarsın. Derleyici her tekrar için gövdeyi bir kez üretir. Aşağıda vec!'e benzer kendi koleksiyon makromuzu yazıyoruz:
// Değişken sayıda ifadeyi alıp bir Vec kuran makro
macro_rules! dizi {
// $x:expr'i virgülle, sıfır+ kez yakala
( $( $x:expr ),* ) => {
{
let mut v = Vec::new();
// Her yakalanan $x için push satırını bir kez üret
$( v.push($x); )*
v
}
};
}
fn main() {
let v = dizi![10, 20, 30]; // vec![10,20,30] gibi
println!("{v:?}"); // [10, 20, 30]
}Çağrı dizi![10, 20, 30] için makro üç kez v.push(...) üretir. $( ... ),* sondaki virgülü de hoş görür (trailing comma), çünkü * sıfır veya daha fazla anlamına gelir ve son ayraç opsiyoneldir. Bu, gerçek vec!'in nasıl çalıştığının basitleştirilmiş halidir.
Birden fazla tekrar değişkenini birlikte yayabilirsin: $( $k:expr => $v:expr ),* deseni anahtar/değer çiftlerini eşler ve transcriber'da $( map.insert($k, $v); )* ile ikisini paralel yayarsın. İkisinin tekrar sayısı eşit olmalıdır.
Bu bölümde
- $( ... )sep* / + / ? ile değişken sayıda token eşlenir
- * sıfır+, + bir+, ? sıfır-veya-bir; ayraç (örn. virgül) opsiyoneldir
- Matcher'da yakalanan tekrar, transcriber'da $(...)* ile geri yayılır
- vec!-benzeri variadic makrolar bu mekanizmayla yazılır
04 Hijyen
Hijyen (hygiene), bir makronun içinde ürettiği isimlerin çağıran koddaki isimlerle asla çakışmamasıdır. C önişlemcisinde bu kavram yoktur; Rust'ta dilin garantisidir. Bu, C'den gelen biri için en çarpıcı farktır.
Önce C felaketini görelim. Klasik bir SWAP makrosu içinde geçici bir tmp kullanır; eğer çağıran kodun da tmp adlı bir değişkeni varsa, makro onu sessizce gölgeler ya da bozar:
#define SWAP(a, b) { int tmp = a; a = b; b = tmp; }
int main(void) {
int tmp = 7, y = 9;
// Makronun 'tmp'si çağıranın 'tmp'siyle çakışır — bug
SWAP(tmp, y); // genişleyince iç içe iki tmp
return tmp;
}Rust'ta aynı durum imkânsızdır. Makro içinde tanımlanan değişken, makronun kendi "sözdizimsel bağlamına" (syntax context) aittir; çağıranın aynı adlı değişkeni tamamen ayrı sayılır. Derleyici bunları farklı renklerle işaretlenmiş gibi düşünür:
macro_rules! takas {
($a:expr, $b:expr) => {{
let tmp = $a; // bu 'tmp' makroya ait, çağırana sızmaz
$a = $b;
$b = tmp;
}};
}
fn main() {
let mut tmp = 7; // çağıranın 'tmp'si — DOKUNULMAZ
let mut y = 9;
takas!(tmp, y);
// İki 'tmp' farklı bağlamda; çakışma yok
println!("{tmp} {y}"); // 9 7
}Burada makronun içindeki tmp ile main'deki tmp aynı yazılmış olmasına rağmen derleyici için farklı iki değişkendir. Hijyen sayesinde takas doğru çalışır ve çağıranın tmp'si bozulmaz. C'deki ad çakışması sınıfı baştan yok olur.
Hijyen yalnızca yerel değişkenleri değil, yolları (path) da kapsar. Makron Vec, Option gibi bir öğeye atıfta bulunuyorsa, çağıran modülde aynı adda bir tip tanımlıysa karışıklık olur. Çözüm $crate metadeğişkenidir: makronun tanımlandığı crate'in köküne sabit bir yol verir.
// $crate, makro hangi crate'te tanımlıysa onun köküne çözülür
// Çağıranın kendi 'Hata' tipi olsa bile doğru tip kullanılır
macro_rules! sonuc_olustur {
($v:expr) => { $crate::Sonuc::Tamam($v) };
}Yol hijyeni tam değildir — Rust yerel değişken hijyenini güçlü garanti eder ama tipler/yollar için $crate kullanmak senin sorumluluğundur. Yayımlanan (public) bir makro yazıyorsan, içerideki her standart tip ve fonksiyon yoluna $crate:: ya da ::std:: tam yol ver.
Bu bölümde
- Hijyen: makro içi isimler çağıranın isimleriyle çakışmaz
- C'deki tmp/SWAP gölgeleme bug'ı Rust'ta tasarımdan dolayı yok
- Aynı yazılan iki değişken farklı sözdizimsel bağlamlara ait sayılır
- $crate ile yol hijyeni sağlanır; public makrolarda tam yol kullan
05 Yaygın yerleşik makrolar
Rust standart kütüphanesinin en sık kullanılan araçlarının çoğu aslında makrodur. Neden? Çünkü hepsi fonksiyonun yapamadığı bir şey ister: değişken sayıda argüman ya da derleme-zamanı denetim.
println!, format! ve vec! birer makrodur, fonksiyon değil — bu yüzden çağrıda ! görürsün. Bir fonksiyon değişken sayıda argüman ya da derleme zamanında doğrulanan bir format string'i kabul edemezdi; makrolar edebilir.
| Makro | İş | Neden makro |
|---|---|---|
println! | biçimli yazdırma | variadic + format denetimi |
format! | String üretme | variadic + format denetimi |
vec! | Vec kurma | variadic kurucu |
assert! | çalışma-zamanı koşul | başarısızlıkta ifade metnini basar |
dbg! | ifade + değer ayıklama | dosya/satır + ifade metni |
matches! | desen eşleşme testi | desen (pat) argümanı alır |
todo! / unimplemented! | panik yer tutucu | asla-dönmez (!) tip |
fn main() {
let ad = "x3";
// format string DERLEME ZAMANINDA denetlenir:
// eksik argüman veya yanlış {} derleme hatasıdır
println!("merhaba {ad}");
let v = vec![1, 2, 3];
// dbg! ifadeyi VE değerini, dosya:satır ile basar, değeri döndürür
let toplam = dbg!(v.iter().sum::<i32>());
// matches! bir desen alır; if let'in kısa hâli
let bos = matches!(v.first(), Some(&1)); // true
assert!(toplam == 6, "toplam 6 olmalı");
println!("{bos}");
}En kritik kazanç format denetimidir: println!("{}", x) içinde argüman sayısı veya tipi yanlışsa, hata derleme zamanında çıkar — çalışma zamanında değil. C'deki printf format string'i bir runtime karakter dizisidir; tip uyuşmazlığı çoğu zaman fark edilmez ve UB'ye yol açar. Rust'ta bu sınıf hata baştan imkânsızdır çünkü format string'i makro tarafından derleme anında ayrıştırılır.
dbg! hem ifadenin metnini hem değerini, hem de dosya/satır numarasını stderr'e basar ve değeri geri döndürür — yani bir ifadenin ortasına dbg!(...) sarmalayıp akışı bozmadan ayıklama yapabilirsin. Bunu bir fonksiyon yapamaz; çünkü argümanın kaynak metnine erişmesi gerekir.
Bu bölümde
- println!/format!/vec!/assert!/dbg!/matches!/todo! birer makrodur
- Makro olmalarının sebebi: variadic argüman veya derleme-zamanı denetim
- Format string derleme anında denetlenir; printf'in runtime UB'si yok
- dbg! ifade metnine + dosya/satıra erişir; fonksiyon bunu yapamaz
06 cargo expand
Makro hatasını ayıklamanın en güçlü yolu, makronun ürettiği gerçek kodu görmektir. cargo expand tüm makroları açıp ortaya çıkan düz Rust kodunu basar — C'de gcc -E ile önişlemci çıktısını görmenin Rust karşılığıdır.
Bir makro beklenmedik davrandığında, kafa karıştırıcı hata mesajları yerine doğrudan üretilen koda bakmak istersin. cargo expand tam bunu yapar: kaynaktaki vec![...], #[derive(...)] ve kendi makrolarını çözüp altlarındaki gerçek kodu gösterir. Önce kurulur:
# Tek seferlik kurulum (cargo alt komutu olarak eklenir)
cargo install cargo-expand
# Tüm crate'i genişlet ve düz Rust kodunu bas
cargo expand
# Sadece belirli bir öğeyi genişlet (ör. main fonksiyonu)
cargo expand mainÖrneğin let v = vec![1, 2, 3]; satırını içeren bir program cargo expand ile çalıştırıldığında, makronun açıldığı gerçek kodu görürsün — kabaca şuna benzer:
// vec![1, 2, 3] bu hâle genişler (sadeleştirilmiş):
let v = {
let mut tmp = ::alloc::vec::Vec::new();
tmp.push(1);
tmp.push(2);
tmp.push(3);
tmp
};cargo expand nightly toolchain'in iç API'sini kullanır ama kararlı (stable) Rust üzerinde de çalışır; gerekirse cargo +nightly expand ile açıkça nightly seçebilirsin. Çıktının okunması için rustfmt kuruluysa otomatik biçimlendirilir.
Genişletilmiş çıktı tam derlenebilir Rust değildir — bazı iç (internal) yollar ve öznitelikler içerir. Amacı okumak ve anlamaktır, kopyalayıp derlemek değil. Kendi macro_rules! makronu ayıklarken ilk başvuracağın araç budur.
Bu bölümde
- cargo expand makroların ürettiği düz Rust kodunu gösterir
- C'deki gcc -E (önişlemci çıktısı) görmenin Rust karşılığıdır
- Kurulum: cargo install cargo-expand; kullanım: cargo expand [öğe]
- Makro ayıklamanın ilk aracı; çıktı okumak içindir, derlemek için değil
07 Prosedürel makro türleri
macro_rules! desen-eşleme tabanlı bildirimsel bir araçtır. Prosedürel makrolar (procedural macros) ise farklı bir cins: girdiyi bir TokenStream olarak alıp, üzerinde gerçek Rust kodu çalıştırarak çıktı TokenStream üreten fonksiyonlardır. Esneklik tavanı çok daha yüksektir.
Aradaki temel fark: bildirimsel makro bir desene bakar ve şablon doldurur; prosedürel makro ise bir derleyici eklentisi gibidir — token'ları ayrıştırır, mantık yürütür, istediği kodu programatik olarak üretir. Üç türü vardır:
| Tür | Çağrı biçimi | Kullanım |
|---|---|---|
| function-like | benimki!(...) | macro_rules! gibi ama TokenStream işler |
| attribute | #[benimki] | öğeyi sarmalar/dönüştürür (örn. route) |
| derive | #[derive(Benimki)] | tip için trait impl üretir |
Prosedürel makrolar ayrı bir cratete yaşamak zorundadır ve o crate'in Cargo.toml'unda proc-macro = true işaretlenmelidir. Sebep teknik: bu makrolar derleyicinin kendisi tarafından, hedef kodu derlemeden önce çalıştırılır; dolayısıyla derleyici-eklentisi gibi ayrı derlenmeleri gerekir.
# Prosedürel makro crate'i bu satırı ZORUNLU kılar
[lib]
proc-macro = true
[dependencies]
syn = "2" # TokenStream -> AST ayrıştırma
quote = "1" # AST -> TokenStream üretmeProsedürel makro yazarken neredeyse her zaman iki crate kullanılır: syn gelen TokenStream'i yapısal bir AST'ye ayrıştırır, quote ise quote! { ... } şablonuyla kolayca TokenStream üretmeni sağlar. İkisi proc-macro ekosisteminin fiilî standardıdır.
Prosedürel makro gücü pahalıya gelir: ayrı crate, derleme süresine ek yük, ve macro_rules!'a göre belirgin biçimde daha fazla karmaşıklık. Basit desen-değiştirme işleri için macro_rules! yeterliyse onu seç; proc-macro'yu gerçekten gerektiğinde kullan.
Bu bölümde
- Prosedürel makro TokenStream alır, kod çalıştırır, TokenStream üretir
- Üç tür: function-like, attribute, derive
- Ayrı crate + Cargo.toml'da proc-macro = true zorunludur
- Pratikte syn (ayrıştırma) + quote (üretme) ikilisi kullanılır
08 Türetme (derive) makroları
#[derive(Debug, Clone)] yazdığında aslında prosedürel derive makroları çağırırsın. Bunlar tipinin yapısına bakıp, ilgili trait'in impl bloğunu senin yerine otomatik üretir. Boilerplate'i sıfıra indirmenin yoludur.
Bir #[derive(Trait)] özniteliği, ilgili derive makrosuna tipin tüm tanımını TokenStream olarak verir. Makro bu tanımı ayrıştırır (alanları, generic'leri okur) ve trait'i o tip için gerçekleyen kodu üretir. Debug, Clone, PartialEq gibi standart trait'lerin hepsi bu mekanizmayla gelir.
// Üç satır el yazısı impl yerine tek satır türetme
#[derive(Debug, Clone, PartialEq)]
struct Nokta { x: i32, y: i32 }
fn main() {
let a = Nokta { x: 1, y: 2 };
let b = a.clone(); // Clone türetildi
println!("{a:?}"); // Debug türetildi -> Nokta { x: 1, y: 2 }
assert!(a == b); // PartialEq türetildi
}Kendi özel derive makronu yazmanın iskeleti kavramsal olarak şöyledir: syn ile tip tanımını ayrıştır, ilgi duyduğun bilgiyi (tip adı, alanlar) çıkar, quote! ile impl kodunu üret. Aşağıdaki kısa örnek, bir tipin adını döndüren basit bir AdVer trait'ini türetir:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
// #[derive(AdVer)] bu fonksiyonu tetikler
#[proc_macro_derive(AdVer)]
pub fn ad_ver_turet(girdi: TokenStream) -> TokenStream {
// 1) Token'ları yapısal AST'ye ayrıştır
let ast = parse_macro_input!(girdi as DeriveInput);
let ad = &ast.ident; // tipin adı
// 2) impl bloğunu quote! ile üret
let uretilen = quote! {
impl AdVer for #ad {
fn ad(&self) -> &'static str {
stringify!(#ad)
}
}
};
uretilen.into() // 3) TokenStream olarak geri ver
}Özel derive ne zaman değer? Aynı boilerplate impl'i onlarca tipe elle yazman gerektiğinde. Serde'nin #[derive(Serialize, Deserialize)]'ı bunun en bilinen örneğidir — yüzlerce satır serileştirme kodunu tek satıra indirir.
Bu bölümde
- #[derive(...)] derive makrolarını çağırır; trait impl'i otomatik üretir
- Debug/Clone/PartialEq gibi standart trait'ler bu yolla gelir
- Özel derive: syn ile ayrıştır, quote! ile impl üret, TokenStream döndür
- Çok sayıda tipe aynı boilerplate gerektiğinde değer kazanır (örn. serde)
09 Gerçek örnek
Teoriyi tek bir faydalı makroda toplayalım: anahtar/değer çiftlerinden HashMap kuran bir harita! makrosu. Tekrar, fragment belirteçleri ve hijyen burada bir arada çalışır — ve cargo expand ile ne ürettiğini görürüz.
Python'daki {k: v} sözdiziminin Rust karşılığı yoktur; standart yol HashMap::new() + tekrarlı insert'tür. Bunu tek satıra indiren bir makro hem variadic'tir hem de $k => $v çift desenini paralel yayar:
use std::collections::HashMap;
// $k => $v çiftlerini virgülle, sıfır+ kez eşle
macro_rules! harita {
( $( $k:expr => $v:expr ),* $(,)? ) => {{
let mut m = HashMap::new();
// her çift için bir insert üret (paralel yayım)
$( m.insert($k, $v); )*
m
}};
}
fn main() {
let yaslar = harita!{
"ali" => 30,
"veli" => 25, // sondaki virgül $(,)? ile serbest
};
println!("{:?}", yaslar.get("ali")); // Some(30)
}Burada üç teknik birleşir: $( ... ),* değişken sayıda çifti eşler, $(,)? sondaki opsiyonel virgülü hoş görür, ve makro içindeki m hijyen sayesinde çağıranın değişkenleriyle çakışmaz. cargo expand çalıştırıldığında makronun aslında ne ürettiği netleşir:
// harita!{ "ali" => 30, "veli" => 25 } şuna açılır:
let yaslar = {
let mut m = HashMap::new();
m.insert("ali", 30);
m.insert("veli", 25);
m
};harita!{...} → matcher eşleşir → $(...)* yayılır → insert satırları → HashMapPeki bu makro gerçekten gerekli mi? İşte makrodan kaçınma kuralının özü: bu harita! meşrudur çünkü gerçekten variadic bir kurucu sağlıyor ve dilin sözdizimini anlamlı biçimde genişletiyor — bir fonksiyon bunu yapamaz. Ama tek bir değer alıp dönüştüren işler için makro yazmak yanlıştır; orada fonksiyon daha iyi hata mesajı ve IDE desteği verir.
Makrodan ne zaman kaç: (1) Bir fonksiyon ya da generic aynı işi yapabiliyorsa. (2) Makro çağıranın sözdizimini bozacak kadar "sihirli" hâle geliyorsa. (3) Hata mesajları okunamaz hâle geldiyse. Makro güçlüdür ama her güçlü araç gibi az ve yerinde kullanılır.
Özetle: macro_rules! ile değişken argüman, kod üretimi ve sözdizimi genişletmeyi C önişlemcisinin tüm tuzaklarından arınmış biçimde yaparsın; prosedürel makrolar ve derive ise boilerplate'i programatik olarak yok eder. Aklında tek bir pusula tut: makro son çaredir, ama doğru zamanda eşi yoktur.
Bu bölümde
- harita! makrosu tekrar + çift desen + hijyeni bir arada gösterir
- $(,)? ile sondaki opsiyonel virgül zarif biçimde ele alınır
- cargo expand üretilen düz insert kodunu doğrular
- Makro son çaredir: fonksiyon/generic yetmediğinde, yerinde kullanılır