İçeriğe geç
academia.sh

Ders 09 / 12

Desen Eşleme

Aynı Cember(5.0) değeri üç ayrı desen biçimiyle ayrıştırılıyor — birebir, bağlayıcı, koşullu — ve match'in kendisi bir ifade olarak bütün dallarda aynı tipi veriyor. Sınırlayıcı ölçüm bir yakalayıcı dalın bütünlüğü sağladığını ama yeni bir varyantı sessizce yutabildiğini gösteriyor.

Yayın Güncelleme

Bu derste

  • match'in bir ifade olduğunu ve bütün dallarının aynı tipi vermek zorunda olduğunu gösterir
  • Aynı değeri birebir, bağlayıcı ve koşullu desenle ayrıştırıp üçünün de aynı sınıfta olduğunu doğrular
  • Derleyicinin match dallarında bütünlük istemesinin nedenini açıklar
  • Bir yakalayıcı dalın bütünlüğü sağladığını ama yeni bir varyantı koruyamadığını örnekle gösterir
İçindekiler

Numaralandırmalar dersi, bir Sekil değerinin kaç ayrı biçim alabileceğini ve bu biçimlerin kaynakta nasıl kurulduğunu ölçtü — ama kurulan değerin hangi varyantı taşıdığını okumak işini iki yardımcı fonksiyonun içine, arka plana bıraktı. matchin kendisi hiç açılmadı: hangi desen biçimleri var, bir dal ne zaman diğerinden önce gelir, derleyici neden bütün varyantların ele alınmasını ister? Bu ders tam bu üç soruyu açıyor.

match, Rust’ta bir değeri desenlerle karşılaştırıp eşleşen ilk dalın gövdesini çalıştıran yapıdır. Denetim Akışı dersi matchi zaten bir ifade olarak tanıtmıştı — bir değer üretir ve bütün dallar aynı tipi vermek zorundadır. Bu ders o tanımı derinleştiriyor: aynı değer kaç farklı desen biçimiyle yakalanabilir ve bu biçimlerin hepsi gerçekten aynı sınıfa mı düşer?

Desenle ayrıştırmanın kendisi bu kursta yeni bir kavram değildir; bir değerin biçimine göre dallanma fikri daha önce de kuruldu. Bu derste ölçülen, o fikrin Rust’taki gerçekleştirimine özgü bir zorunluluktur: eksik bırakılan bir dal, kaynağın derlenmesini engeller. Bu zorunluluğun kapsamı ve sınırları aşağıda ayrıntılı ölçülüyor.

match Bir İfadedir, Bütün Dallar Aynı Tipi Verir

Bir match ifadesinin sonucu, hangi dalın eşleştiğine bağlıdır — ama sonucun tipi hangi dalın eşleştiğinden bağımsızdır. Derleyici, matchin her dalının aynı tipte bir değer verdiğini derleme zamanında denetler; bir dal &str, başka bir dal i32 verirse kaynak derlenmez. Bu kural, matchin bir ifade olmasının doğrudan sonucudur: bir ifadenin tek bir tipi vardır ve o tip hangi dalın çalıştığına göre değişemez.

Aşağıdaki koşum, tek bir Sekil::Cember(5.0) değerini üç ayrı fonksiyonla — üç ayrı desen biçimiyle — ayrıştırıyor.

// uc_desen_bicimi.rs — match bir ifadedir, ayni deger uc desen bicimiyle ayristirilir
enum Sekil {
    Bos,
    Cember(f64),
    Dikdortgen(f64, f64),
}

fn siniflandir_birebir(s: &Sekil) -> &'static str {
    match s {
        Sekil::Bos => "bos",
        Sekil::Cember(_) => "cember",
        Sekil::Dikdortgen(_, _) => "dikdortgen",
    }
}

fn siniflandir_baglayici(s: &Sekil) -> String {
    match s {
        Sekil::Bos => "bos".to_string(),
        Sekil::Cember(yaricap) => format!("cember yaricap={}", yaricap),
        Sekil::Dikdortgen(genislik, yukseklik) => format!("dikdortgen {}x{}", genislik, yukseklik),
    }
}

fn siniflandir_kosullu(s: &Sekil) -> &'static str {
    match s {
        Sekil::Cember(yaricap) if *yaricap > 3.0 => "buyuk cember",
        Sekil::Cember(_) => "kucuk cember",
        Sekil::Dikdortgen(genislik, yukseklik) if genislik == yukseklik => "kare",
        Sekil::Dikdortgen(_, _) => "dikdortgen",
        Sekil::Bos => "bos",
    }
}

fn main() {
    let deger = Sekil::Cember(5.0);
    println!("{:<20}: {}", "birebir desen", siniflandir_birebir(&deger));
    println!("{:<20}: {}", "baglayici desen", siniflandir_baglayici(&deger));
    println!("{:<20}: {}", "kosullu desen", siniflandir_kosullu(&deger));

    let kucuk = Sekil::Cember(1.0);
    println!("{:<20}: {}", "kosullu (kucuk)", siniflandir_kosullu(&kucuk));

    let kare = Sekil::Dikdortgen(4.0, 4.0);
    println!("{:<20}: {}", "kosullu (kare)", siniflandir_kosullu(&kare));

    let bos = Sekil::Bos;
    println!("{:<20}: {}", "birebir (bos)", siniflandir_birebir(&bos));
}
birebir desen       : cember
baglayici desen     : cember yaricap=5
kosullu desen       : buyuk cember
kosullu (kucuk)     : kucuk cember
kosullu (kare)      : kare
birebir (bos)       : bos

RV8. — “match’in her dalının aynı tipi verdiği” iddiası burada üç ayrı fonksiyonun kendi dönüş tipi imzasıyla ölçülüyor: siniflandir_birebir ve siniflandir_kosullu &'static str döndürüyor diye imzalarında yazıyor, siniflandir_baglayici String döndürüyor diye yazıyor. Üç fonksiyonun içindeki match ifadelerinin her biri, kendi imzasındaki tek tipe uyuyor; aksi olsaydı kaynak hiç derlenmezdi.

Aynı Değer Üç Desen Biçimiyle Ayrıştırılıyor

siniflandir_birebir, siniflandir_baglayici ve siniflandir_kosullu fonksiyonlarının üçü de aynı deger — Sekil::Cember(5.0) — üzerinde çalıştı, ama her biri değeri farklı bir desen biçimiyle yakaladı.

Birebir desen, yalnızca hangi varyantın eşleştiğine bakar; taşınan veriye hiç dokunmaz. Sekil::Cember(_) => "cember" dalındaki alt çizgi (_), “bu konumda ne olursa olsun” anlamına gelir — yaricapın değeri 5.0 de olsa 0.3 de olsa fark etmez, dal yalnızca varyantın Cember olup olmadığına bakar. Bu, bir önceki dersteki varyant_adi fonksiyonunun yaptığı işin ta kendisidir; orada da her varyant, taşıdığı veri hiç okunmadan yalnızca adıyla ayırt ediliyordu.

Bağlayıcı desen, varyantı eşleştirmekle kalmaz, taşınan veriyi bir ada da bağlar. Sekil::Cember(yaricap) => ... dalında yaricap adı, eşleşen değerin içindeki 5.0a bağlanıyor ve dalın gövdesinde bir metin kalıbının içine yerleştirilerek kullanılabiliyor. Çıktıda görülen cember yaricap=5 tam olarak bu bağlamanın sonucu.

Bağlayıcı desen ile birebir desen arasındaki fark yalnızca “bir ad yazılıp yazılmadığı” değildir; bağlanan ad, dalın gövdesinde gerçekten kullanılabilir bir değişkendir. siniflandir_baglayici fonksiyonunda yaricap, metin kalıbını dolduran bir bağımsız değişken olarak doğrudan geçiriliyor — tıpkı sıradan bir fonksiyon parametresi gibi davranıyor. Birebir desendeki alt çizgi (_) ise bir ad değildir, hiçbir yere bağlanmaz; yalnızca “burada bir değer var, ama onunla ilgilenmiyorum” der.

Koşullu desen, bir bağlayıcı desenin üstüne bir de koşul (if) ekler; buna Rust’ta desen koruyucusu denir. Sekil::Cember(yaricap) if *yaricap > 3.0 => "buyuk cember" dalı yalnızca hem varyant Cember hem de bağlanan yaricap 3.0tan büyükse eşleşir. degerin yarıçapı 5.0 olduğundan bu koşul sağlanıyor ve sonuç "buyuk cember" oluyor; kucuk değeri (Cember(1.0)) aynı dala düşmüyor, çünkü koşul sağlanmıyor ve bir alttaki Sekil::Cember(_) => "kucuk cember" dalına düşüyor.

RV9. — Üç desen biçiminin de “aynı sınıfta” olduğu iddiası, üçünün de bir match ifadesinin gövdesi olarak bir değer üretmesiyle ölçülüyor: hiçbiri bir yan etki için yazılmadı, üçü de doğrudan bir fonksiyonun dönüş değeri oldu. Bir desenin birebir mi, bağlayıcı mı, koşullu mu olduğu, matchin kendisinin ifade olup olmadığını değiştirmiyor — üçü de aynı match ... { desen => değer, ... } kalıbının birer örneği.

Kare örneği bunu bir adım ileri taşıyor: Sekil::Dikdortgen(genislik, yukseklik) if genislik == yukseklik => "kare" dalı, iki alanı aynı anda bağlayıp ikisini birbiriyle karşılaştırıyor. kare değeri Dikdortgen(4.0, 4.0) olduğundan bu koşul da sağlanıyor. Bir desen koruyucusu, yalnızca bağlanan tek bir adı değil, birden fazla bağlanan adı aynı anda kullanabilir — bağlamanın kendisi desenin işi, koşulun kendisi ise sıradan bir mantıksal ifadedir. genislik == yukseklik karşılaştırması, matche özgü bir yazım değildir; iki ondalık sayının herhangi bir bağlamda karşılaştırılmasıyla aynı kuralları izler, yalnızca bu kez desenin bağladığı iki ada uygulanıyor.

siniflandir_kosullu fonksiyonundaki dal sırası rastgele değildir. Sekil::Cember(yaricap) if *yaricap > 3.0 dalı, koşulsuz Sekil::Cember(_) => "kucuk cember" dalından önce yazılıyor; match dalları yukarıdan aşağıya sırayla denenir ve ilk eşleşen dal kazanır. Eğer bu iki dal yer değiştirseydi, Cember(_) deseni hiçbir koşul taşımadığından her Cember değerini — büyük olsun küçük olsun — yakalar ve alttaki koşullu dala hiçbir değer ulaşmazdı. Aynı kural Dikdortgen dalları için de geçerli: kare kontrolü, koşulsuz Dikdortgen dalından önce gelmek zorunda, aksi hâlde her dikdörtgen — kare olsun olmasın — genel dala düşerdi.

Bu sıralama zorunluluğu, siniflandir_birebir ve siniflandir_baglayici fonksiyonlarında görülmüyor, çünkü oradaki dalların hiçbiri birbirinin alt kümesi değil: Bos, Cember(_) ve Dikdortgen(_, _) ayrık varyantlardır, hangi sırada yazılırlarsa yazılsınlar aynı değeri yakalarlar. Sıralamanın önem kazanması, yalnızca aynı varyantı hedefleyen birden fazla dal olduğunda — koşullu desenlerde olduğu gibi — ortaya çıkıyor.

Bütünlük: Eksik Dal Neden Derlenmiyor

matchin Rust’a özgü bir kısıtı vardır: bütün dallar, eşleşen değerin her olası varyantını kapsamak zorundadır. Buna bütünlük denir. Yukarıdaki siniflandir_birebir fonksiyonu Sekil::Bos, Sekil::Cember(_) ve Sekil::Dikdortgen(_, _) olmak üzere üç dal yazdı — Sekilin bildirimindeki üç varyantın hepsini. Eğer bu dallardan biri, örneğin Sekil::Dikdortgen(_, _) => "dikdortgen" satırı, kaynaktan çıkarılsaydı, kalan kaynak derlenmezdi.

Bu ders derlenmeyen bir kaynağı kod bloğuna koymuyor, çünkü derlenmeyen kaynak zaten bloğa girmiyor — reddedilen bir yazımın çıktısı olmaz, dolayısıyla koşumla doğrulanacak bir sayı da üretmez; ama reddin nedeni burada tarif edilebilir. Derleyici, match s { Sekil::Bos => ..., Sekil::Cember(_) => ... } gibi eksik bir yazımı gördüğünde, sin çalışma zamanında Sekil::Dikdortgen değerini taşıyabileceğini biliyor — çünkü bu, enum Sekil bildiriminin izin verdiği bir varyant. Elindeki dallardan hiçbiri bu olasılığı karşılamadığından, derleyici hangi kolun çalışacağını söyleyemez ve kaynağı reddeder. Bu ret, çalışma zamanında “beklenmeyen değer” diye bir hatanın hiç oluşmamasını sağlar: eksik bir dal, programın çalışmasından önce, derleme sırasında yakalanır.

RV10. — Bütünlüğün gerçekten zorunlu olduğu iddiası burada ayrı bir derleyici çağrısıyla ölçülmüyor — derlenmeyen kaynak kod bloğuna girmediği için — ama dolaylı bir kanıt var: yukarıdaki üç fonksiyonun her biri, Sekilin üç varyantının tamamını kapsıyor. Eğer bütünlük zorunlu olmasaydı, bu üç dalı tam yazmanın bir gerekçesi kalmazdı; üçü de yalnızca gerekli olduğu için oradadır.

Bütünlük denetimi, kaynaktaki matchin eşleştiği değerin tipine bakarak çalışır; taşınan verinin hangi değerleri alabileceğine bakmaz. Sekil::Cember(f64) sonsuz sayıda yarıçap değeri taşıyabilir, ama derleyici bu sonsuzluğu tek tek denetlemez — yalnızca Cember varyantının bir dalda ele alınıp alınmadığını sorar. Numaralandırmalar dersinde ölçülen “varyant sayısı durum sayısı değildir” ayrımı burada da geçerlidir: bütünlük varyant düzeyinde bir garantidir, durum düzeyinde değil. Bir dal Sekil::Cember(yaricap) yazdığı sürece, yaricapın hangi değeri taşıdığından bağımsız olarak o varyant kapsanmış sayılır.

Sınırlayıcı Ölçüm: Yakalayıcı Dal Bütünlüğü Sağlar Ama Korumaz

Bütünlük kuralını karşılamanın bir kestirme yolu vardır: son dal olarak _ => ... yazmak. Bu dal, kalan bütün varyantları — kaynakta kaçı olursa olsun — tek seferde kapsar ve derleyiciyi tatmin eder. Ama bu tatmin, dalın gerçekten her varyantı ayrı ayrı düşündüğü anlamına gelmez; yalnızca hiçbirini atlamadığı anlamına gelir.

// butunluk_yakalayici.rs — yakalayici dal butunluk sagliyor ama korumuyor
enum SekilEski {
    Cember(f64),
    Dikdortgen(f64, f64),
}

enum SekilYeni {
    Cember(f64),
    Dikdortgen(f64, f64),
    #[allow(dead_code)]
    Ucgen(f64, f64, f64),
}

fn etiket_eski(s: &SekilEski) -> String {
    match s {
        SekilEski::Cember(yaricap) => format!("cember r={}", yaricap),
        SekilEski::Dikdortgen(genislik, yukseklik) => format!("dikdortgen {}x{}", genislik, yukseklik),
    }
}

fn etiket_yeni(s: &SekilYeni) -> String {
    match s {
        SekilYeni::Cember(yaricap) => format!("cember r={}", yaricap),
        SekilYeni::Dikdortgen(genislik, yukseklik) => format!("dikdortgen {}x{}", genislik, yukseklik),
        _ => "diger".to_string(),
    }
}

fn main() {
    let eski_cember = SekilEski::Cember(2.0);
    let eski_dikdortgen = SekilEski::Dikdortgen(2.0, 2.0);
    println!("{:<20}: {}", "eski: cember", etiket_eski(&eski_cember));
    println!("{:<20}: {}", "eski: dikdortgen", etiket_eski(&eski_dikdortgen));

    let yeni_cember = SekilYeni::Cember(2.0);
    let yeni_dikdortgen = SekilYeni::Dikdortgen(2.0, 2.0);
    let yeni_ucgen = SekilYeni::Ucgen(3.0, 4.0, 5.0);
    println!("{:<20}: {}", "yeni: cember", etiket_yeni(&yeni_cember));
    println!("{:<20}: {}", "yeni: dikdortgen", etiket_yeni(&yeni_dikdortgen));
    println!("{:<20}: {}", "yeni: ucgen", etiket_yeni(&yeni_ucgen));
}
eski: cember        : cember r=2
eski: dikdortgen    : dikdortgen 2x2
yeni: cember        : cember r=2
yeni: dikdortgen    : dikdortgen 2x2
yeni: ucgen         : diger

SekilEski iki varyant taşıyor ve etiket_eski ikisini de açıkça, adıyla ele alıyor — hiçbir yakalayıcı dal yok, çünkü iki dal zaten bütünlüğü sağlıyor. Sonra kaynak SekilYeniye geçiyor: aynı iki varyant (Cember, Dikdortgen) korunuyor, ama üçüncü bir varyant (Ucgen) ekleniyor. etiket_yeni fonksiyonu, etiket_eskiyle aynı iki dalı taşıyor ve üçüncü bir dal yazmak yerine _ => "diger".to_string() ekliyor.

Bu kaynak sorunsuz derleniyor — bütünlük sağlanmış durumda, çünkü _ her varyantı kapsıyor. Ama Ucgen(3.0, 4.0, 5.0) gibi gerçek verisi olan bir değer bu koda girdiğinde, sonuç yalnızca "diger": üçgenin üç kenar uzunluğu (3.0, 4.0, 5.0) hiçbir yerde okunmuyor, hiçbir hesaba katılmıyor. Ucgen, Cember ve Dikdortgenden farklı bir varyant olduğu hâlde, kod düzeyinde ayrı bir muamele görmüyor.

RV11. — “Yakalayıcı dal bütünlüğü sağlar ama korumaz” iddiası burada iki numaralandırma yan yana koşturularak ölçülüyor: SekilEski ile SekilYeni aynı iki varyantı (Cember, Dikdortgen) taşıyor ve ikisi de aynı iki girdiyle (2.0 ve 2.0, 2.0) aynı sonucu veriyor — "cember r=2" ve "dikdortgen 2x2". Fark yalnızca SekilYeninin üçüncü bir varyant eklemesinde ve etiket_yeninin bu varyantı _e bırakmasında. Çıktının son satırı — yeni: ucgen : diger — bu yeni varyantın, kendine özgü bir işlem almadan yakalayıcıya düştüğünü gösteriyor.

Bu, bütünlüğün ne garanti ettiğini kesin çizgilerle sınırlıyor: derleyici yalnızca “her varyant için bir dal var mı” sorusuna evet diyor; “her varyant kendi mantığıyla ele alındı mı” sorusuna cevap vermiyor. Bir enuma sonradan varyant eklemek, _ dalı olan bir matchi derleme hatası vermeden geçirir — geliştiricinin yeni varyantı fark etmesi için derleyiciden bir bildirim gelmez, çünkü bütünlük kuralı zaten sağlanmıştır. Yakalayıcısız bir matchta durum farklıdır: yeni varyant eklendiği an, o varyantı kapsamayan her match derlenmeyi durdurur ve geliştiriciyi doğrudan o satıra götürür.

Bu fark, etiket_eski ile etiket_yeni fonksiyonlarının yazım biçiminde de görünür. etiket_eski, SekilEskiin iki varyantını da adıyla yazdığı için, SekilEskiye üçüncü bir varyant eklenseydi kaynak derlenmeyi durdururdu — kod, hangi satırın eksik olduğunu doğrudan gösterirdi. etiket_yeni ise _ dalını taşıdığından, SekilYeniye dördüncü bir varyant eklense bile derlenmeye devam eder; yeni varyant, önceki üç varyanttan hiçbirine benzemese de aynı "diger" sonucuna düşer. İki fonksiyon da bütünlük kuralını sağlıyor, ama yalnızca biri, gelecekteki bir değişikliği geliştiricinin gözüne sokuyor.

Bu üç ders — yapılar, numaralandırmalar, desen eşleme — bir veri modelinin üç ayrı sorusuna yanıt verdi: bir değer hangi alanları taşır, kaç ayrı biçim alabilir ve bu biçimler kaynaktan nasıl okunur. Şimdiye kadar her ölçüm tek bir değer üzerinden yapıldı — tek bir Nokta, tek bir Sekil. Ama gerçek bir programın verisi neredeyse hiçbir zaman tek bir değerden ibaret değildir; çok sayıda değer bir arada tutulmak zorundadır.

Özet

  • match bir ifadedir; bütün dalları aynı tipte bir değer vermek zorundadır, aksi hâlde kaynak derlenmez.
  • Aynı değer birebir (yalnızca varyantı denetleyen), bağlayıcı (veriyi bir ada bağlayan) ve koşullu (bağlanan veriye bir koşul ekleyen) desenlerle ayrıştırılabilir; üçü de aynı match yapısının birer örneğidir.
  • Bütünlük, bir matchin eşleşen değerin bütün olası varyantlarını kapsamasını zorunlu kılar; eksik bırakılan bir varyant kaynağı derlenmez hâle getirir.
  • Bir yakalayıcı dal (_) bütünlüğü sağlar ama korumaz: sonradan eklenen bir varyantı kendi mantığıyla ele almadan sessizce kapsar.

Sonraki Adım

Bu ders, tek bir Sekil değerinin nasıl ayrıştırıldığını ölçtü. Ama gerçek programlar tek bir değeri değil, çok sayıda değeri bir arada tutar — bir listedeki bütün şekiller, bir sözlükteki bütün adlar. Şimdiye kadar her ders tek bir değişkene bağlanan tek bir değeri konu etti; sıradaki ders bu sınırı kaldırıyor ve aynı soruyu bir grup değere taşıyor: çok sayıda değer aynı anda nasıl tutulur ve bu koleksiyonların değiştirici yöntemleri (ekleme, çıkarma) hangi sınıfa düşer?

İ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