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(°er));
println!("{:<20}: {}", "baglayici desen", siniflandir_baglayici(°er));
println!("{:<20}: {}", "kosullu desen", siniflandir_kosullu(°er));
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(¥i_cember));
println!("{:<20}: {}", "yeni: dikdortgen", etiket_yeni(¥i_dikdortgen));
println!("{:<20}: {}", "yeni: ucgen", etiket_yeni(¥i_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
matchbir 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ı
matchyapı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.