Ders 01 / 12
Bildirimsel Makrolar
Kursun ölçüm çekirdeği program içinden koşturulur ve on iki gözlemi dört sınıfa yerleştirir — sürdürüyor, devredildi, dilin dışında, denetlenmiyor; dağılım 4/3/2/3'tür ve bu ders yalnız 'sürdürüyor' sınıfına düşen iki gözlemi derinleştirir: makronun ürettiği gövde tip denetiminden geçiyor, makronun içindeki ad çağıranınkini bozmuyor.
Yayın Güncelleme
Bu derste
- Bildirimsel makronun ürettiği gövdenin, elle yazılmış bir gövdeyle aynı biçimde tip denetiminden geçtiğini koşturarak gösterir
- Makro hijyeninin, makro içindeki bir adın çağıranın aynı adlı değişkenini bozmadığını doğrular
- Aynı makronun iki farklı türle çağrıldığında ikisinin de derlendiğini, ama makronun hangisinin anlamlı olduğunu bilmediğini örnekle açıklar
İçindekiler
Eşzamanlılık ve Asenkron Rust kursu boyunca bir işin ilerlemek için neye ihtiyaç duyduğu ölçüldü — bir kanal, bir kilit, bir bekleme noktası. Dört kurs boyunca hiç sorgulanmayan tek bir varsayım vardı: derleyici arkada duruyor ve her adımı denetliyor. Bu kurs tam o varsayımı ölçüye açıyor. On iki farklı düzenekte aynı soru sorulacak: derleyicinin güvencesi burada nerede bitiyor?
Yanıt dört sınıftan birine düşüyor ve bu dört ad kurs boyunca değişmiyor. Bu ilk ders on ikisini birlikte, tek bir çekirdek programla koşturuyor; sonra ikisini — makronun ürettiği gövdenin denetimi ve makronun kendi iç adlarının çağıranınkiyle karışmaması — derinleştiriyor. Kalan on soru bu kursun sonraki on bir dersine düşüyor; bu ders yalnız makrolarla ilgili ikisini alıyor, çünkü bu konu makrolarla ilgili.
Dört Sınıf
| Sınıf | Tanım |
|---|---|
| sürdürüyor | Derleyici denetlemeye devam ediyor |
| devredildi | Denetim durdu; koşulu yazan üstlendi |
| dilin dışında | Doğruluğu başka bir dil, araç ya da hedef belirliyor |
| denetlenmiyor | Hiç kimse denetlemiyor |
Bu dörtlü, Standart Kütüphane ve Akışlar kursunda (M08/K03) sorulan soruyla karışmaz: orada bir davranışın garantisini kimin verdiği — arayüz mü, gerçekleştirim mi, çağıran mı — soruluyordu, yani bir sözleşmenin tarafları. Burada sorulan, derleyicinin kendi denetiminin nerede durduğu. Bildirimsel makrolar bu dört sınıfın ilkine, sürdürüyor sınıfına, en açık örneği veriyor: bir makro çağrısının ürettiği kaynak, elle yazılmış bir kaynaktan derleyici açısından ayırt edilemez ve aynı kurallarla denetlenir.
Bunun neden şaşırtıcı olmadığını görmek için makronun ne zaman devreye girdiğine bakmak gerekiyor. Bir bildirimsel makronun genişlemesi, tür denetiminden önce oluyor: derleyici önce kaynaktaki her makro çağrısını kendi ürettiği metinle değiştiriyor, sonra bütün dosyayı sıradan Rust kaynağı gibi okuyup türleri, ödünç kurallarını ve isim çözümlemesini sırasıyla uyguluyor. Bu sıralama, makronun ürettiği kaynağın neden “sürdürüyor” sınıfına düştüğünü açıklıyor: makro genişlediği anda ortadan kalkıyor, geriye yalnız sıradan Rust kaynağı kalıyor ve o kaynağı denetleyen mekanizma, elle yazılmış bir dosyayı denetleyenle aynı.
Ölçüm Çekirdeği
Kursun geri kalanında kullanılan çekirdek guvence.rsdir. On iki gözlemi tek bir gözlem
defterine yazar ve sonunda sınıf başına toplamı basar. Üçüncü taraf kasa kullanmaz: yabancı
fonksiyon arayüzü, C kitaplığının zaten bağlanmış iki işleviyle kurulur, dışarıdan hiçbir
paket derlemeye girmez. Kursun geri kalan on bir dersi bu çekirdeğin yalnız kendi kesitini
alacak; yalnız bu ders, on iki gözlemin tamamını bir arada gösteriyor.
MK1. gozle işlevi ve defter demeti (String, String, String üçlüsü) çekirdeğin
kendi tasarım kararıdır. Bir gözlemin hangi sınıfa girdiğine karar veren şey her satırın
kaynaktaki biçimidir; gozle çağrısı yalnız bu kararı bir tabloya kaydeder ki on iki gözlem
tek koşumda karşılaştırılabilsin.
// guvence.rs — derleyicinin guvencesi nerede bitiyor: surduruyor mu, yazana mi devredildi,
// dilin disinda mi, yoksa hic kimse denetlemiyor mu.
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
// Dilin disindaki iki islev: govdelerini derleyici hic gormedi.
extern "C" {
fn abs(x: c_int) -> c_int;
fn strlen(s: *const c_char) -> usize;
}
// Bildirimsel makro: urettigi govde de tip denetiminden geciyor.
macro_rules! ikiye_katla {
($ad:ident) => {
fn $ad(x: i32) -> i32 {
x * 2
}
};
}
ikiye_katla!(uretilen_islev);
// Makro hijyeni: icerideki ad, cagiranin adiyla carpismiyor.
macro_rules! kendi_adini_kullan {
() => {{
let x = 99;
x
}};
}
/// Bu belge satiri islevin **en buyuk** degeri dondurdugunu soyluyor.
/// Islev en kucugu donduruyor. Hicbir arac bu celiskiyi denetlemiyor.
fn belgesi_yanlis(a: i32, b: i32) -> i32 {
if a < b {
a
} else {
b
}
}
// cfg ile disarida kalan govde: derleyici bunu hic gormuyor.
#[cfg(feature = "acilmayan")]
fn hic_denetlenmeyen() {
bu_islev_hic_tanimlanmadi();
let _x: i32 = "metin";
}
const SURUM_DIZESI: &str = "0.1.0";
// Sozlesmesi yazana devredilen islev: cagiran indeksin gecerli oldugunu garanti eder.
unsafe fn ikinci_ogeyi_al(v: &[i32]) -> i32 {
*v.get_unchecked(1)
}
fn gozle(defter: &mut Vec<(String, String, String)>, ad: &str, sinif: &str, kanit: String) {
defter.push((ad.to_string(), sinif.to_string(), kanit));
}
fn main() {
let mut defter: Vec<(String, String, String)> = Vec::new();
let v = vec![10, 20, 30, 40];
// --- derleyici surduruyor ---
let uretilen = uretilen_islev(21);
gozle(
&mut defter,
"makro uretilen govde",
"surduruyor",
format!("cagri sonucu {}", uretilen),
);
let x = 1;
let makro_degeri = kendi_adini_kullan!();
gozle(
&mut defter,
"makro hijyeni",
"surduruyor",
format!("disaridaki x={} makro {}", x, makro_degeri),
);
let sinirda = v.get(10);
gozle(
&mut defter,
"dilim sinir denetimi",
"surduruyor",
format!("onuncu oge {:?}", sinirda),
);
let isaretci = v.as_ptr();
let (ilk_ham, blok_ici) = unsafe {
// Ayni blokta iki islem: biri gercekten denetimsiz, oteki hala denetleniyor.
(*isaretci, v.get(10))
};
gozle(
&mut defter,
"unsafe blok icindeki denetim",
"surduruyor",
format!("ham okuma {} ama onuncu oge {:?}", ilk_ham, blok_ici),
);
// --- yazana devredildi ---
let ham = unsafe { *isaretci.add(2) };
gozle(
&mut defter,
"ham isaretci okuma",
"devredildi",
format!("okunan {} guvenli karsiligi {}", ham, v[2]),
);
let denetimsiz = unsafe { *v.get_unchecked(1) };
gozle(
&mut defter,
"denetimsiz erisim",
"devredildi",
format!("okunan {} guvenli karsiligi {}", denetimsiz, v[1]),
);
let sozlesmeli = unsafe { ikinci_ogeyi_al(&v) };
gozle(
&mut defter,
"sozlesmesi devredilen islev",
"devredildi",
format!("okunan {}", sozlesmeli),
);
// --- dilin disinda ---
let mutlak = unsafe { abs(-7) };
gozle(
&mut defter,
"yabanci islev cagrisi",
"dilin disinda",
format!("sonuc {}", mutlak),
);
let metin = CString::new("merhaba").unwrap();
let uzunluk = unsafe { strlen(metin.as_ptr()) };
gozle(
&mut defter,
"yabanci uzunluk olcumu",
"dilin disinda",
format!("uzunluk {} rust karsiligi {}", uzunluk, "merhaba".len()),
);
// --- kimse denetlemiyor ---
let belge_sonucu = belgesi_yanlis(3, 9);
gozle(
&mut defter,
"belge metninin dogrulugu",
"denetlenmiyor",
format!("belge en buyuk diyor, sonuc {}", belge_sonucu),
);
gozle(
&mut defter,
"surum dizesinin anlami",
"denetlenmiyor",
format!("yazili deger {}", SURUM_DIZESI),
);
gozle(
&mut defter,
"derlemeye girmeyen govde",
"denetlenmiyor",
String::from("tanimsiz cagri ve tip hatasi tasiyor, program yine de derlendi"),
);
// --- rapor ---
println!("{:<32}{:<16}{}", "gozlem", "sinif", "kanit");
for (ad, sinif, kanit) in &defter {
println!("{:<32}{:<16}{}", ad, sinif, kanit);
}
println!();
for s in ["surduruyor", "devredildi", "dilin disinda", "denetlenmiyor"] {
let adet = defter.iter().filter(|(_, k, _)| k == s).count();
println!("{:<16}{}", s, adet);
}
println!("toplam gozlem: {}", defter.len());
}
gozlem sinif kanit makro uretilen govde surduruyor cagri sonucu 42 makro hijyeni surduruyor disaridaki x=1 makro 99 dilim sinir denetimi surduruyor onuncu oge None unsafe blok icindeki denetim surduruyor ham okuma 10 ama onuncu oge None ham isaretci okuma devredildi okunan 30 guvenli karsiligi 30 denetimsiz erisim devredildi okunan 20 guvenli karsiligi 20 sozlesmesi devredilen islev devredildi okunan 20 yabanci islev cagrisi dilin disinda sonuc 7 yabanci uzunluk olcumu dilin disinda uzunluk 7 rust karsiligi 7 belge metninin dogrulugu denetlenmiyor belge en buyuk diyor, sonuc 3 surum dizesinin anlami denetlenmiyor yazili deger 0.1.0 derlemeye girmeyen govde denetlenmiyor tanimsiz cagri ve tip hatasi tasiyor, program yine de derlendi surduruyor 4 devredildi 3 dilin disinda 2 denetlenmiyor 3 toplam gozlem: 12
MK2. On iki gözlemin dördü sürdürüyor sınıfına düşüyor ve bu dörtten ikisi bu konunun
kapsamında: üretilen bir gövdenin çağrısı (cagri sonucu 42) ve makro içindeki bir adın
çağıranın aynı adlı değişkenini bozmaması (disaridaki x=1 makro 99). Bu ders geri kalan
onunu bir kez koşturup bir kenara bırakıyor, yalnız bu ikisini derinleştiriyor.
Üretilen Gövdenin Tip Denetimi
Çekirdekteki üretim makrosunun çağrısı, uretilen_islev adlı bir işlevi hiçbir elle yazılmış
fn satırı olmadan var ediyor. Bu işlevin uretilen_islev(21) çağrısında 42 döndürmesi,
kaynakta bir gövdenin durduğunu gösteriyor ama bunun ne kadar derine indiğini göstermiyor.
Aşağıdaki örnek aynı üretimi bir kez daha kurup üretilen işlevi, makronun hiç bilmediği bir
kısıtlamanın — genel bir sınırın — içine sokuyor.
// govde_genelleme.rs — makro uretimi bir govde, genel bir sinirlamaya sokuluyor.
macro_rules! govde_uret {
($ad:ident) => {
fn $ad(x: i32) -> i32 {
x * 3
}
};
}
govde_uret!(uc_katla);
fn uygula<F: Fn(i32) -> i32>(islev: F, deger: i32) -> i32 {
islev(deger)
}
fn main() {
let sonuc = uygula(uc_katla, 7);
println!("uc_katla genel sinir icinde: {}", sonuc);
}
uc_katla genel sinir icinde: 21
MK3. Üretim makrosunun bu çağrısı yalnız uc_katla adlı bir işlevi doğurdu; işlevin uygula içindeki
F: Fn(i32) -> i32 sınırını sağlayıp sağlamadığını makronun kendisi hiç sınamadı — bu sınama
üretilen kaynak uygulaya geçirildiğinde, sıradan bir işlev geçirilmiş gibi, derleyicinin
kendisi tarafından yapıldı. Üretilen gövde yalnız doğrudan çağrıldığında değil, bir genel
sınırın içine sokulduğunda da denetleniyor: makro hangi bağlamda kullanılacağını önceden
bilmiyor, ama ürettiği kaynak her bağlamda aynı kurallara tabi.
Bu ayrım küçük görünse de sonucu büyük: üretim makrosunu tanımlayan yer, uc_katlanın hangi
genel sınırlara gireceğini hiç bilmiyor — o sınır çok sonra, tamamen ayrı bir yerde, uygula
tanımlanırken yazılıyor. Buna rağmen ikisi tutarlı çalışıyor, çünkü aralarındaki tek bağ
üretilen kaynağın imzası: fn uc_katla(x: i32) -> i32. Makro bu imzayı üretmekten
sorumlu, imzanın nerede kullanılacağından değil; kalan her şeyi, tıpkı elle yazılmış bir
işlevde olduğu gibi, çağıran taraf ve derleyici birlikte çözüyor.
Makro Hijyeni
Çekirdekteki hijyen makrosunun çağrısı, kendi içinde bir x bağlıyor ve onu döndürüyor;
çağıranın kendi xi (1) bu çağrıdan etkilenmiyor (disaridaki x=1 makro 99). Bu tek bir
çağrıyla gösterilen bir sonuç. Aşağıdaki örnek aynı makroyu aynı kapsamda iki kez çağırarak
hem dıştaki adla hem birbirleriyle çakışıp çakışmadığını sınıyor.
// hijyen_ikinci.rs — ayni makro iki kez cagriliyor, ic adlari ne disariyla ne kendi aralarinda karisiyor.
macro_rules! kendi_adini_kullan {
() => {{
let x = 99;
x
}};
}
fn main() {
let x = 7;
let birinci = kendi_adini_kullan!();
let ikinci = kendi_adini_kullan!() + 1;
println!("disaridaki x={} birinci={} ikinci={}", x, birinci, ikinci);
}
disaridaki x=7 birinci=99 ikinci=100
Dıştaki x yedi olarak kalıyor, iki çağrının ikisi de kendi 99unu üretiyor ve ikinci çağrı
bu değeri kendi hesabında (+ 1) kullanabiliyor — makronun içindeki ad her genişlemede kendi
kapsamına ait, ne dıştaki xle ne bir önceki genişlemenin xiyle karışıyor. Hijyen burada
bir isim çakışmasını önleyen bir kural değil, her makro genişlemesinin kendi adlandırma
alanını taşıdığı bir düzenek: metin düzeyinde aynı x sözcüğü yan yana üç kez geçiyor, ama
üçü de derleyici için ayrı birer bağlama.
Bu düzeneğin pratik karşılığı şu: bir makro tanımlayan taraf, kendi gövdesinde istediği kadar yardımcı ad kullanabiliyor ve çağıranın hangi adları kullandığını hiç bilmek zorunda kalmıyor. Hijyen olmasaydı, makronun içindeki her yardımcı adın çağıranın kapsamındaki adlarla çakışmadığından ayrıca emin olmak gerekirdi — bu da makroyu, adları önceden tahmin edilemeyen her yerde kullanmayı riskli kılardı. Rust’ın makro sistemi bu riski dilin kendisinde, çağıran ile makro yazan arasında bir sözleşme yazmadan kapatıyor.
Sınırlayıcı Ölçüm
Önceki iki bölüm, makronun ürettiği kaynağın gerçek Rust kaynağı gibi denetlendiğini gösterdi. Bu denetim bir sınır taşıyor: makro yalnız sözdizimi düzeyinde çalışıyor, yerine koyduğu parçanın ne anlama geldiğini bilmiyor. Aynı makro iki ayrı türle çağrılıp bu sınır gösterilir.
// buyuk_secici.rs — ayni makro iki turde cagriliyor; ikisi de tip denetiminden geciyor.
macro_rules! buyugu_secen_govde_uret {
($ad:ident, $t:ty) => {
fn $ad(a: $t, b: $t) -> $t {
if a > b { a } else { b }
}
};
}
buyugu_secen_govde_uret!(buyuk_tam_sayi, i32);
buyugu_secen_govde_uret!(buyuk_mantiksal, bool);
fn main() {
println!("buyuk_tam_sayi(3, 9)={}", buyuk_tam_sayi(3, 9));
println!("buyuk_mantiksal(false, true)={}", buyuk_mantiksal(false, true));
}
buyuk_tam_sayi(3, 9)=9 buyuk_mantiksal(false, true)=true
Makronun kendisinde hiçbir tür sınırı yazılı değil; $t:ty yalnız “burada bir tür adı
gelecek” diyor, hangi türlerin bu işlemi anlamlı kılacağını sormuyor. i32 için büyüğü
seçmek sıradan bir karşılaştırma; bool için de aynı sözdizimi geçerli — false, trueden
küçük sayılıyor ve ikinci işlev true döndürüyor. Derleyici ikisini de aynı gerekçeyle kabul
ediyor: her ikisi de karşılaştırma işlecini destekleyen bir tür. Hangi çağrının çağıran için
gerçekten bir anlam taşıdığını, hangisinin yalnız türü uyduğu için derlendiğini makro da
derleyici de söyleyemiyor — bu ayrımı yalnız kaynağı okuyan taraf yapabiliyor.
Bu sınır, önceki iki bölümde gösterilen denetimi geçersiz kılmıyor; onu daraltıyor. Üretilen gövdenin bir genel sınıra uyup uymadığı, içindeki adın çağıranınkiyle çakışıp çakışmadığı — ikisi de kesin sorulardır ve derleyici ikisini de kesin yanıtlıyor. Ama “bu işlem bu tür için mantıklı mı” sorusu bambaşka bir sorudur ve derleyicinin elindeki tek araç tür uygunluğu: bir tür karşılaştırma işlecini destekliyorsa, o karşılaştırmanın çağıranın niyetine uyup uymadığına bakmadan kabul ediyor. Sözdizimi düzeyinde çalışan bir araç için bu beklenmedik bir sınır değil, tanımın kendisi — makro bir metni başka bir metne dönüştürüyor, o metnin okuyan için ne ifade ettiğine karışmıyor.
Özet
- Kurs boyunca dört sınıf değişmiyor: sürdürüyor, devredildi, dilin dışında, denetlenmiyor; on iki gözlemin dağılımı 4/3/2/3’tür.
- Bu ders sürdürüyor sınıfına düşen iki gözlemi derinleştirdi: makronun ürettiği gövde bir genel sınırın içinde bile sıradan kaynak gibi denetleniyor, makronun içindeki ad her genişlemede kendi kapsamını taşıyor ve çağıranınkiyle karışmıyor.
- Üretilen gövdenin denetimi doğrudan çağrıyla sınırlı değil: üretilen bir işlev, bir trait sınırının içine sokulduğunda da aynı kurallarla sınanıyor.
- Makro sözdizimi düzeyinde çalışıyor: aynı makro birbirinden anlamca uzak iki türle çağrıldığında ikisi de derleniyor, çünkü derleyici yalnız türlerin uyup uymadığına bakıyor.
Sonraki Adım
Bu ders, üretimin aynı derleme adımında — aynı dosyada — gerçekleştiğini gösterdi: makro tanımı ve makronun çağrısı bir arada durabiliyordu, derleyici tek bir çağrıda hem tanımı hem genişlemeyi hem tür denetimini yapabiliyordu. Sıradaki ders aynı “sürdürüyor” sınıfını bambaşka bir kurgudan ölçüyor: üretim artık aynı dosyada değil, ayrı bir derleme adımında oluyor ve tüketen kaynak üretilen kaynağı ancak derleyici ikinci kez çağrıldığında görebiliyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.