Rust'ta makro yazmanın en yaygın yolu macro_rules! ile bildirimsel (declarative) makro tanımlamaktır. Bir makro, match ifadesine benzeyen kurallardan oluşur: her kuralın sol tarafı çağrıdaki kaynak kodun yapısıyla eşleşen bir desen, sağ tarafı ise eşleşme olursa onun yerine konacak koddur. Bu değiştirme derleme sırasında yapılır; yani makro, çalışma zamanında değil derleyici kodun anlamını yorumlamadan önce genişler.
Bu yazıda macro_rules! sözdizimini, parça belirteçlerini (fragment specifier), tekrar desenlerini, hijyen kurallarını, kapsam ve dışa aktarma davranışını çalışan örneklerle ele alıyoruz. Prosedürel makrolara yalnızca kısa bir özetle değineceğiz. println!, vec! ve format! gibi hazır makroların biçim belirteçleri için Display ve Debug formatlama yazısına, #[derive] için derive yazısına bakabilirsiniz.
Makro ile fonksiyon arasındaki fark
Kitap makroları başka kod yazan kod, yani metaprogramlama olarak tanımlar. Fonksiyonlar da tekrarı azaltır ama makroların fonksiyonlarda olmayan yetenekleri vardır. Bir fonksiyonun imzası parametre sayısını ve tipini sabitler; makro ise println!("merhaba") ve println!("merhaba {}", ad) örneğinde olduğu gibi değişken sayıda girdi alabilir. Makrolar derleyici kodun anlamını yorumlamadan önce genişlediği için, örneğin verilen bir tip için trait uygulaması üretebilir; fonksiyon bunu yapamaz, çünkü çalışma zamanında çağrılır ve trait uygulamasının derleme anında hazır olması gerekir.
Bedeli de vardır: makro tanımları Rust kodu yazan Rust kodu olduğu için okunması, anlaşılması ve bakımı fonksiyonlardan zordur. Kitabın vurguladığı bir başka fark da sıralamadır: bir dosyada makroyu çağırmadan önce tanımlamanız ya da kapsama almanız gerekir, fonksiyonları ise herhangi bir yerde tanımlayıp her yerden çağırabilirsiniz.
fn main() {
yaz!();
}
macro_rules! yaz {
() => { println!("yazıldı") };
}
// error: cannot find macro `yaz` in this scopeDerleyici mesajın devamında aynı adda bir makronun daha sonra tanımlandığını söyler ve tanımı çağrının önüne taşımanızı önerir. Tanımı main fonksiyonunun üstüne aldığınızda kod derlenir. Genel kural şudur: kendinize bir makro yazmadan önce aynı işi fonksiyon, generic ya da trait ile yapıp yapamayacağınızı düşünün; makro gerçekten gerekli olduğunda değerlidir.
İlk makro: kurallar ve eşleşme sırası
Bir macro_rules! tanımı ad ve bir ya da daha fazla kuraldan oluşur. Her kuralın iki parçası vardır: neyi eşleyeceğini anlatan eşleştirici (matcher) ve yerine ne konacağını anlatan aktarıcı (transcriber). İkisi de ayraç içinde yazılır. Makro çağrıldığında derleyici kuralları sırayla dener ve ilk başarılı eşleşmeyi genişletir; Reference'a göre bu genişletme bir hata üretirse sonraki kurallar denenmez.
macro_rules! selam {
() => {
println!("Merhaba!")
};
($ad:expr) => {
println!("Merhaba, {}!", $ad)
};
($ad:expr, $kez:literal) => {
for _ in 0..$kez {
selam!($ad);
}
};
}
fn main() {
selam!();
selam!("Ayşe");
let isim = String::from("Mehmet");
selam![isim, 2];
}
// çıktı:
// Merhaba!
// Merhaba, Ayşe!
// Merhaba, Mehmet!
// Merhaba, Mehmet!Birkaç ayrıntıya dikkat edin. $ad:expr ifadesindeki dolar işareti bunun bir makro değişkeni olduğunu, expr ise hangi tür sözdizimiyle eşleşeceğini belirtir. Üçüncü kural makronun kendisini çağırıyor; Reference'a göre makro adları çağrı noktasından arandığı için özyinelemeli kullanım mümkündür. Son satırda köşeli parantez kullandık: eşleştiricinin en dıştaki ayraçları herhangi bir ayraç çiftiyle eşleşir, bu yüzden selam!(), selam![] ve selam!{} aynı kurala gider. vec![...] yazımı da bu esneklikten gelir.
Parça belirteçleri (fragment specifiers)

Eşleştiricide $ad:belirteç biçimi, belirtilen türde bir sözdizimi parçasını yakalar ve $ad adına bağlar. Aktarıcıda yalnızca $ad yazılır, çünkü tür eşleştiricide zaten belirtilmiştir. Bir makro değişkeni aktarıcıda birden fazla kez kullanılabilir ya da hiç kullanılmayabilir. The Reference: Macros by example sayfasındaki geçerli belirteçler şunlardır:
| Belirteç | Neyle eşleşir | Örnek |
|---|---|---|
| expr | Bir ifade | 1 + 2, f(x), "metin" |
| expr_2021 | expr gibi, ama alt çizgi ve const blok ifadeleri hariç | Eski edition davranışı için |
| ident | Tanımlayıcı veya anahtar kelime (_ hariç) | sayac, Urun |
| ty | Bir tip | u32, Vec<String> |
| path | Tip yolu | std::collections::HashMap |
| pat | Desen (2021 sonrası üst düzey | dahil) | Some(x), 1..=5 |
| pat_param | Üst düzey | içermeyen desen | Some(x) |
| stmt | Sondaki noktalı virgül olmadan deyim | let a = 1 |
| block | Süslü parantezli blok | { a + b } |
| item | Öğe: fn, struct, impl ... | fn f() {} |
| literal | Değişmez değer, isteğe bağlı eksi işaretiyle | 42, -1, "a" |
| lifetime | Yaşam süresi belirteci | 'a |
| meta | Bir özniteliğin içeriği | derive(Debug) |
| vis | Görünürlük niteleyicisi, boş olabilir | pub, pub(crate) |
| tt | Tek token ağacı | Tek token ya da ayraçlı grup |
Rust 2024 edition'da expr belirtecinin davranışı değişti. Edition Guide'a göre 2021 ve öncesinde expr üst düzeyde const { ... } blokları ve _ ifadeleriyle eşleşmiyordu; 2024'te bunlarla da eşleşiyor. Eski davranışı korumak isteyenler için expr_2021 belirteci eklendi ve cargo fix --edition geçiş sırasında expr kullanımlarını expr_2021 yapar. Belge, çoğu durumda yeni ifadeleri desteklemek için expr belirtecini korumanızı önerir. Diğer edition farkları için 2024 edition yazısına bakın.
Önemli bir ayrıntı: yakalanan bir parçayı başka bir makroya ilettiğinizde, ikinci makro onu türü belli opak bir sözdizimi ağacı olarak görür. Örneğin $l:expr olarak yakaladığınız 3 değerini, (3) değişmezini bekleyen başka bir makroya geçirirseniz eşleşmez. Reference'a göre yalnızca ident, lifetime ve tt parçaları ikinci makroda değişmez token'larla eşleşebilir. Parçayı olduğu gibi iletmeniz gereken yerlerde tt kullanın.
Tekrar desenleri: $( ... ),* ve arkadaşları

Değişken sayıda girdi almak için tekrar edilecek kısım $( ... ) içine yazılır, ardından isteğe bağlı bir ayırıcı ve bir tekrar operatörü gelir. Operatörler üç tanedir: * sıfır veya daha fazla, + en az bir, ? sıfır ya da bir tekrar demektir. Ayırıcı herhangi bir token olabilir, ama ayraçlar ve tekrar operatörleri olamaz; en sık virgül ve noktalı virgül kullanılır. ? en fazla bir kez eşleştiği için ayırıcıyla birlikte kullanılamaz.
macro_rules! en_buyuk {
($x:expr) => { $x };
($x:expr, $($geri:expr),+) => {{
let a = $x;
let b = en_buyuk!($($geri),+);
if a > b { a } else { b }
}};
}
macro_rules! topla {
($($x:expr),* $(,)?) => { 0 $(+ $x)* };
}
fn main() {
println!("{}", en_buyuk!(3));
println!("{}", en_buyuk!(4, 9, 2, 7));
println!("{}", topla!());
println!("{}", topla!(1, 2, 3,));
}
// çıktı:
// 3
// 9
// 0
// 6en_buyuk! klasik bir özyineleme kalıbıdır: ilk kural tek elemanı ele alır, ikinci kural ilk elemanı ayırıp geri kalanı makronun kendisine verir. Çift süslü parantez, genişlemenin tek bir blok ifadesi olmasını sağlar; böylece içerideki let deyimleri çağıranın kapsamına sızmaz. topla! makrosundaki $(,)? parçası sondaki isteğe bağlı virgülü kabul etmenin yaygın yoludur.
Aktarıcıda tekrarlara ek kurallar uygulanır. Reference'a göre bir makro değişkeni aktarıcıda, eşleştiricide olduğu sayıda, türde ve iç içelik sırasında tekrar içinde görünmelidir. Yani $( $i:ident ),* ile yakalanan $i aktarıcıda tekrar dışında tek başına kullanılamaz. Ayrıca aynı tekrarda birden fazla değişken varsa hepsinin aynı sayıda parçaya bağlanmış olması gerekir; Reference örneğinde (a, b, c; d, e, f) çağrısı geçerli, (a, b, c; d, e) ise geçersizdir.
Makroyla kod üretmek: ident, ty ve stringify!
Makroların en güçlü kullanımlarından biri tekrarlayan öğe tanımlarını üretmektir. Aşağıdaki makro bir yapı adı ile alan listesini alıyor, yapıyı tanımlıyor ve alan adlarını metin olarak döndüren bir ilişkili fonksiyon ekliyor. stringify! makrosu kendisine verilen token'ları derleme anında bir metin değişmezine çevirir.
macro_rules! kayit {
($ad:ident { $($alan:ident : $tip:ty),* $(,)? }) => {
#[derive(Debug)]
#[allow(dead_code)]
struct $ad {
$($alan: $tip),*
}
impl $ad {
fn alanlar() -> &'static [&'static str] {
&[$(stringify!($alan)),*]
}
}
};
}
kayit!(Urun { kod: u32, ad: String, fiyat: f64 });
fn main() {
let u = Urun { kod: 7, ad: "Klavye".to_string(), fiyat: 349.9 };
println!("{u:?}");
println!("{:?}", Urun::alanlar());
}
// çıktı:
// Urun { kod: 7, ad: "Klavye", fiyat: 349.9 }
// ["kod", "ad", "fiyat"]Burada makro bir öğe konumunda, yani fonksiyonların dışında çağrıldı. Reference'a göre makrolar ifade, deyim, öğe (trait, impl ve dış öğeler dahil), tip veya desen olarak genişleyebilir. #[allow(dead_code)] özniteliğini ekledik, çünkü derleyici türetilen Debug uygulamasını ölü kod analizinde saymaz ve alanların okunmadığı uyarısını verir.
Hijyen: makro içindeki değişkenler neden çakışmaz?
Reference, macro_rules! makrolarının karma bölgeli (mixed-site) hijyene sahip olduğunu söyler. Döngü etiketleri, blok etiketleri ve yerel değişkenler makronun tanımlandığı yerde aranır; diğer adlar, örneğin fonksiyonlar, çağrıldığı yerde aranır. Pratik sonucu şudur: makronun içinde tanımladığınız bir let x, çağıranın aynı adlı değişkeniyle çakışmaz.
macro_rules! iki_kat {
($e:expr) => {{
let x = $e;
x * 2
}};
}
macro_rules! kare_yanlis {
($e:expr) => { $e * $e };
}
fn main() {
let x = 10;
println!("{}", iki_kat!(x + 1));
println!("{x}");
let mut sayac = 0;
let mut artir = || { sayac += 1; sayac };
println!("{}", kare_yanlis!(artir()));
}
// çıktı:
// 22
// 10
// 2İlk çağrıda makronun içindeki x ile çağıranın x değişkeni ayrı kaldı: sonuç 22, dışarıdaki x hâlâ 10. Aynı kural tersine de işler: bir makro çağrısında tanımlanan yerel değişkeni başka bir makro çağrısından okuyamazsınız. Üçüncü satır ise hijyenin çözmediği bir sorunu gösterir: kare_yanlis! ifadeyi iki kez yerleştirdiği için artir() iki kez çalıştı ve 1 ile 2 çarpılarak 2 bulundu, 1 değil. Yan etkisi olan ifadeleri bir kez değerlendirmek için iki_kat! makrosundaki gibi önce bir yerel değişkene bağlayın.
Eşleşme hataları ve takip kuralları
Hiçbir kural çağrıyla eşleşmezse derleyici beklenmeyen token'ı gösterir. Aşağıdaki makro tek ifade bekliyor, biz iki ifade verdik:
macro_rules! selam {
($ad:expr) => {
println!("Merhaba, {}!", $ad)
};
}
fn main() {
selam!("Ali", "Veli");
}
// error: no rules expected `,`Reference ayrıca eşleşme sırasında ileriye bakma (lookahead) yapılmadığını belirtir. Derleyici çağrıyı token token ilerleyerek tek anlamlı biçimde ayrıştıramazsa yerel belirsizlik hatası verir; Reference'taki ($($i:ident)* $j:ident) örneği bu yüzden derlenmez. Bir başka kısıt da takip kümeleridir: bir parça belirtecinden sonra yalnızca güvenle gelebileceğine karar verilmiş token'lar yazılabilir. Bu, gelecekte dile eklenecek sözdiziminin mevcut makroları bozmaması içindir.
macro_rules! cift {
($a:expr $b:expr) => { ($a, $b) };
}
fn main() {
let _ = cift!(1 2);
}
// error: `$a:expr` is followed by `$b:expr`, which is not allowed for `expr` fragmentsDerleyici notunda izin verilen token'ları da listeler: expr ve stmt parçalarından sonra yalnızca =>, , veya ; gelebilir. ty ve path parçalarından sonra daha geniş bir küme serbesttir; ident, tt, literal gibi belirteçlerin ise bir kısıtı yoktur. Çözüm genellikle parçaların arasına virgül ya da noktalı virgül koymaktır.
Kapsam, #[macro_export] ve $crate
Makroların kapsamı tarihsel nedenlerle diğer öğelerden farklıdır. Varsayılan olan metinsel kapsam, kaynak dosyadaki sıraya dayanır: makro tanımından sonra, içinde bulunduğu modül kapanana kadar geçerlidir ve alt modüllere, hatta başka dosyalara da uzanabilir. Aynı adla ikinci kez tanımlamak hata değildir; yeni tanım eskisini gölgeler.
Bir makroyu crate dışına açmak için #[macro_export] özniteliği kullanılır. Reference'a göre bu öznitelik makroyu crate'in köküne yerleştirir ve yol tabanlı çözümlemeyle erişilebilir kılar; makro bir alt modülde tanımlı olsa bile crate::makro_adi! biçiminde çağrılır. Dışa aktarılan bir makronun içinde kendi crate'inizdeki öğelere başvurmak için $crate anahtar değişkenini kullanın; böylece makroyu kullanan crate o öğeleri içe aktarmamış olsa da yol doğru çözülür. Reference, $crate ile başvurulan öğenin çağrı noktasından görünür olması gerektiğini, yani özel bir fonksiyonu bu yolla dışarı açamayacağınızı da ekler. Modüller ve görünürlük için modül sistemi yazısına bakın.
Uygulamalı mini örnek: harita! makrosu
Standart kütüphanede vec! var ama sıralı bir harita için hazır bir değişmez sözdizimi yok. Aşağıdaki makro anahtar => değer çiftlerini alıp bir BTreeMap kurar. Standart kütüphane yollarını ::std:: ile tam yazdık; böylece makroyu kullanan kodda farklı bir BTreeMap içe aktarılmış olsa bile doğru tip kullanılır.
mod araclar {
#[macro_export]
macro_rules! harita {
() => { ::std::collections::BTreeMap::new() };
($($k:expr => $v:expr),+ $(,)?) => {{
let mut m = ::std::collections::BTreeMap::new();
$( m.insert($k, $v); )+
m
}};
}
}
fn main() {
let fiyatlar = harita! {
"elma" => 30,
"armut" => 45,
"çilek" => 120,
};
println!("{fiyatlar:?}");
let bos: std::collections::BTreeMap<&str, i32> = crate::harita!();
println!("boş mu: {}", bos.is_empty());
}
// çıktı:
// {"armut": 45, "elma": 30, "çilek": 120}
// boş mu: trueexpr parçasından sonra => gelmesi takip kurallarına uygundur. Makro araclar modülünde tanımlı olsa da #[macro_export] sayesinde crate::harita! yolu çalışıyor. Çıktıda anahtarlar sıralı geliyor, çünkü BTreeMap anahtarlarını sıralı tutar. Bu sıralama Türkçe alfabe kurallarına göre değil, str karşılaştırmasına göre yapılır; çıktıda görüldüğü gibi çilek, ASCII harfle başlayan anahtarların ardından geldi. Koleksiyon seçimi için BTreeMap, HashSet ve VecDeque yazısına bakabilirsiniz.
Prosedürel makrolara kısa bir bakış
Kitap, macro_rules! dışında üç tür prosedürel makrodan söz eder: yapı ve enum'lara #[derive] ile eklenen özel türetme makroları, herhangi bir öğeye uygulanabilen öznitelik benzeri makrolar ve fonksiyon çağrısına benzeyen ama argümanındaki token'lar üzerinde çalışan fonksiyon benzeri makrolar. Bunlar desen eşlemek yerine bir fonksiyon gibi çalışır: girdi olarak bir TokenStream alır ve çıktı olarak yine bir TokenStream üretir. Bu tip Rust ile birlikte gelen proc_macro crate'inde tanımlıdır.
Kitaba göre prosedürel makro tanımları, özel bir crate türüne sahip kendi crate'lerinde bulunmak zorundadır. Kitaptaki örnek, girdiyi ayrıştırmak ve kod üretmek için topluluk crate'lerini kullanır; bunlar resmî belgelerin dışında kaldığı için burada ayrıntıya girmiyoruz. Basit tekrarlar için macro_rules! çoğu zaman yeterli ve çok daha az zahmetlidir.
Sık yapılan hatalar

- Makroyu çağrıdan sonra tanımlamak; metinsel kapsam nedeniyle derleyici makroyu bulamaz.
- Yan etkili bir ifadeyi aktarıcıda birden fazla kez kullanmak; ifade her seferinde yeniden çalışır.
- Genişlemeyi tek bloğa sarmamak ve
letdeyimlerinin beklenmedik yerde kalmasına yol açmak. exprparçasından sonra boşlukla başka bir parça yazmak; araya virgül veya noktalı virgül gerekir.- Yakalanan bir
exprparçasını değişmez token bekleyen başka bir makroya iletmek; buradattgerekir. - Dışa aktarılan makroda yardımcı öğelere
$crateolmadan başvurmak. - Fonksiyonla yapılabilecek bir işi makroyla yapıp okunabilirliği gereksiz yere düşürmek.
Sık Sorulan Sorular
Makronun neye genişlediğini nasıl görebilirim?
Kararlı derleyicide en pratik yol küçük örneklerle çıktıyı denemek ve stringify! ile ara sonuçları yazdırmaktır. Derleyici hata mesajlarında, uyarının bir makrodan kaynaklandığını ve ayrıntılı makro geri izinin gece sürümlerinde -Z macro-backtrace ile alınabileceğini de belirtir.
Makro adlarında neden ünlem işareti var?
Ünlem işareti çağrının bir makro olduğunu gösterir. Tanımda ad ünlemsiz yazılır: macro_rules! selam; çağrıda ise selam!() kullanılır.
Bir makro trait uygulaması üretebilir mi?
Evet. Makro derleme anında genişlediği için impl blokları üretebilir. Örneğin birden fazla tip için aynı trait uygulamasını tek bir makro çağrısıyla yazabilirsiniz. Trait kavramı için trait yazısına bakın.
Makroları test edebilir miyim?
Evet. Makro çağrısı normal kod ürettiği için çıktısını birim testlerinde assert_eq! ile karşılaştırabilirsiniz. Dışa aktarılan makroları belgelendirme örnekleriyle de sınayabilirsiniz; bunun inceliklerini rustdoc yazısında bulabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Rust öğrenme rehberi · Rust nedir? · derive ile trait türetme · Display ve Debug formatlama · Trait tanımlama · Modül sistemi