İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat