Ders 08 / 12
Numaralandırmalar
Bir Sekil değeri üç ayrı biçimde kurulabiliyor — hiç veri taşımadan, tek bir sayı taşıyarak, iki sayı taşıyarak — ve üçü de aynı tip altında sayılıyor; varyant kurmak bir ifade, enum bildiriminin kendisi bir öğe. Sınırlayıcı ölçüm varyant sayısının durum sayısıyla karıştırılmaması gerektiğini gösteriyor.
Yayın Güncelleme
Bu derste
- Bir varyant kurmanın ifade, enum bildiriminin öğe olduğunu gösterir
- Veri taşıyan ve taşımayan varyantların aynı tip altında sayıldığını açıklar
- Varyant sayısı ile bir değerin taşıyabileceği durum sayısı arasındaki farkı örnekle ayırt eder
İçindekiler
Yapılar dersi, bir Noktanın her zaman hem x hem y taşıdığını ölçtü: yapı, sabit bir
alan kümesine bağlı, tek bir biçime sahip bir değerdir. Ama bir programın modellemesi
gereken veri çoğu zaman “ya şu ya bu” biçiminde birden fazla olası şekle bürünür — bir
şeklin hiç çizilmemiş olması, bir çember olması ya da bir dikdörtgen olması gibi. Yapı
bunu tek başına karşılayamaz; bu dersin konusu olan numaralandırma (enum), tam bu boşluğu
dolduruyor.
Bir numaralandırma, bir değerin aynı tip altında birden fazla ayrı varyanttan birini
taşıyabilmesini kurar. Önceki dersle aynı soru burada da geçerli: bir varyant kurmak
çalıştırıldığında bir değer mi üretiyor, yoksa enum bildiriminin kendisi mi bir şey
üretiyor? Yanıt yapılarla aynı düzendedir, ama bu kez alan sayısı değil varyant sayısı
ile bir değerin taşıyabileceği durum sayısı arasındaki fark da ölçülüyor.
Bir numaralandırmayı, aynı işlevi görmeyen bir sabitler kümesinden ayıran şey, her
varyantın isteğe bağlı olarak kendi verisini taşıyabilmesidir. Ayrı ayrı tanımlanmış üç
sabit — BOS, CEMBER, DIKDORTGEN gibi — birbirinden bağımsız üç addır; aralarında
“hangisi seçili” sorusuna derleyicinin garanti verdiği bir yanıt yoktur. Bir enum ise
tek bir tip kurar ve o tipin her değeri, o an hangi varyantı taşıdığını kendi içinde bilir.
Bu dersin ölçtüğü ilk şey, bu tek tipliğin öğe/ifade ekseninde nasıl göründüğüdür.
Bildirim Yine Bir Öğedir, Varyant Kurmak Bir İfadedir
enum Sekil { Bos, Cember(f64), Dikdortgen(f64, f64) } satırı, tıpkı bir önceki dersteki
struct bildirimi gibi, çalıştırıldığında hiçbir şey üretmez. Bu satır derleyiciye üç ayrı
varyantı olan bir tip tanıtır; kendisi bir öğedir. Buna karşılık Sekil::Cember(2.0)
yazımı çalıştırıldığında bir değer üretir — bir tam sayı yerine, Cember varyantını taşıyan
ve içinde 2.0ı barındıran bir Sekil değeri. Bu bir ifadedir; sonucu bir değişkene
bağlanabilir, bir listeye eklenebilir, bir fonksiyona geçirilebilir.
RV5. — “enum bildiriminin tek başına hiçbir şey üretmediği” iddiası, tıpkı bir önceki
dersteki struct örneğinde olduğu gibi, ayrı bir koşumla değil aşağıdaki programın
yapısıyla doğrulanıyor: enum Sekil { ... } satırı mainden önce duruyor ve hiçbir
yazdırma çağrısı doğrudan o satırı tetiklemiyor; görünen her çıktı, varyantların
kurulduğu ve kullanıldığı satırlardan geliyor.
Bos, Cember ve Dikdortgen üç ayrı varyant adıdır ve üçü de tek bir enum Sekil { }
bildirimi içinde, virgülle ayrılarak sıralanır. Cember(f64) ile Dikdortgen(f64, f64)
arasındaki fark parantez içindeki tip sayısıdır: biri tek bir f64 taşır, öbürü iki
f64 taşır; Bosun parantezi bile yoktur, çünkü hiç veri taşımaz. Bu üç biçim — parantezsiz,
tek alanlı, çok alanlı — bir enum bildiriminin izin verdiği bütün varyant türlerini
kapsıyor.
Aşağıdaki koşum, Sekil tipinden dört değer kurup her birinin hangi varyanta ait olduğunu
ve alanı ne kadar olduğunu basıyor.
// sekil_enum.rs — enum bildirimi bir oge, varyant kurmak bir ifadedir
use std::collections::BTreeSet;
enum Sekil {
Bos,
Cember(f64),
Dikdortgen(f64, f64),
}
fn varyant_adi(s: &Sekil) -> &'static str {
match s {
Sekil::Bos => "Bos",
Sekil::Cember(_) => "Cember",
Sekil::Dikdortgen(_, _) => "Dikdortgen",
}
}
fn alan(s: &Sekil) -> f64 {
match s {
Sekil::Bos => 0.0,
Sekil::Cember(yaricap) => std::f64::consts::PI * yaricap * yaricap,
Sekil::Dikdortgen(genislik, yukseklik) => genislik * yukseklik,
}
}
fn main() {
let liste = vec![
Sekil::Bos,
Sekil::Cember(2.0),
Sekil::Cember(5.0),
Sekil::Dikdortgen(3.0, 4.0),
];
println!("{:<20}: {}", "tasinan deger sayisi", liste.len());
for (i, s) in liste.iter().enumerate() {
println!(
"{:<20}: {:<11} alan={:.2}",
format!("liste[{}] varyanti", i),
varyant_adi(s),
alan(s)
);
}
let ayri_varyantlar: BTreeSet<&str> = liste.iter().map(varyant_adi).collect();
println!("{:<20}: {}", "ayri varyant sayisi", ayri_varyantlar.len());
}
tasinan deger sayisi: 4 liste[0] varyanti : Bos alan=0.00 liste[1] varyanti : Cember alan=12.57 liste[2] varyanti : Cember alan=78.54 liste[3] varyanti : Dikdortgen alan=12.00 ayri varyant sayisi : 3
liste, dört ayrı çağrıyla kuruldu ve her çağrı bir ifadeydi: Sekil::Bos, hiç veri
taşımadan bile bir Sekil değeridir; Sekil::Cember(2.0) bir ondalık sayı taşıyarak,
Sekil::Dikdortgen(3.0, 4.0) iki ondalık sayı taşıyarak aynı sonuca varır. Üçü de
Vec<Sekil> içine push edilebildi — derleyici için hepsi aynı tip, Sekil.
varyant_adi ve alan fonksiyonları burada bir araç olarak kullanılıyor: her ikisi de
match ile bir değerin hangi varyantı taşıdığını okuyor ve varyanta özgü bir sonuç
üretiyor. matchin kendi kuralları ve dalların bütünlüğü sıradaki dersin konusu; burada
yalnızca veri taşıyan bir varyanttan (Cember, Dikdortgen) o veriye ulaşmanın bir yol
gerektirdiği görülüyor — veri taşımayan Bos varyantından ise okunacak bir alan yok, alan
fonksiyonu onun için doğrudan 0.0 veriyor.
alan fonksiyonunun verdiği sayılar bunu somutlaştırıyor: Cember(2.0)nin alanı
, Cember(5.0)in alanı .
İkisi de aynı formülü kullanıyor — yarıçapın karesiyle pi sayısının çarpımı — ama taşınan
sayı farklı olduğu için sonuç da farklı.
Dikdortgen(3.0, 4.0)ün alanı ise 12.00; bu varyant tek bir sayı değil iki sayı taşıyor
ve alan fonksiyonu ikisini de okuyup çarpıyor. Bos varyantı hiçbir sayı taşımadığından,
onun için hesaplanacak bir alan yok — alan bu durumu ayrı bir dalda 0.0 ile karşılıyor.
Varyant adlarını saymak için burada karma küme değil sıralı küme (BTreeSet) kullanıldı,
oysa yalnızca .len() okunuyor ve bir kümenin eleman sayısı gezinme sırasından
bağımsızdır — bu tek satır için karma küme de aynı sonucu verirdi. Tercih burada bir
zorunluluktan değil, kurs boyunca sürecek bir alışkanlıktan geliyor: çıktısı koşumdan
koşuma değişmeyen bir yapı gerektiğinde sıralı yapı varsayılan seçimdir. Bu seçimin ne
zaman zorunlu, ne zaman yalnızca tutarlılık olduğu Sıralı ve Küme Yapıları dersinin
konusu.
Veri Taşıyan ve Taşımayan Varyantlar Aynı Tipte Sayılır
Sekil tipinin üç varyantından biri (Bos) hiç veri taşımıyor, ikisi (Cember,
Dikdortgen) taşıyor. Yukarıdaki koşumda liste.len() dördü de saydı; Sekil tipi
hepsini eşit ağırlıkta tutuyor. Bu, Rust’ın numaralandırmasını bir listeden (“hangi
seçenek?”) ayıran temel özelliktir: yalnızca bir ad değil, isteğe bağlı olarak bir de
yük taşıyabilir. Veri taşımayan bir varyant, veri taşıyanın yükü sıfır olan özel bir
durumu değildir — ikisi de aynı enum bildiriminin eşit değerli birer üyesidir.
Bu eşitlik Vec<Sekil> gibi bir koleksiyonun neden mümkün olduğunu da açıklıyor. Bir
koleksiyon tek bir tip taşır; eğer Bos, Cember ve Dikdortgen üç ayrı tip olsaydı,
üçünü aynı listede tutmak mümkün olmazdı. enum bunu tek bir tip altında birleştirerek
çözüyor: Sekil değişkeninin taşıdığı asıl varyant çalışma zamanında bellekte saklanıyor,
ama derleme zamanında Sekil tek bir tip olarak görülüyor.
RV6. — “Veri taşıyan ve taşımayan varyantların aynı tipte sayıldığı” iddiası burada
Vec<Sekil> içine dördünün birden konabilmesiyle ölçülüyor: eğer varyantlar farklı
tiplerde sayılsaydı, derleyici liste içindeki dördüncü öğeyi (Dikdortgen) ilkinden
(Bos) farklı bir tip olarak reddederdi. Derlemenin sorunsuz geçmesi, tasinan deger sayisi: 4 satırının dördünü de saymasıyla birlikte bu eşitliğin doğrudan kanıtıdır.
Varyant adları tek başlarına değil, tip adıyla birlikte yazılır: Sekil::Cember(2.0),
Cember(2.0) değil. Bu öneki gerektiren şey, aynı adın birden fazla enumda varyant
olarak kullanılabilmesidir — başka bir tipte de Cember adında bir varyant tanımlanmış
olsaydı, Sekil:: öneki olmadan hangi tipin Cemberinden söz edildiği belirsiz kalırdı.
Bu, bir yapı değerinin kurulmasından (Nokta { x: 3, y: 4 }) farklı bir noktadır: yapı
kurma sözdiziminde tip adı zaten baştadır ve tekrar bir öneke gerek kalmaz, ama bir varyant
kurma sözdiziminde tip adı ile varyant adı iki ayrı ad olduğundan aralarına :: konur.
Bu tek tiplilik, her Sekil değerinin hangi varyantı taşıdığını kendi içinde bilmesiyle
mümkün olur: varyant_adi fonksiyonu tam olarak bu bilgiyi okuyor; alan fonksiyonu ise
hem hangi varyant olduğunu okuyor hem de ona göre farklı alanlara erişiyor. Bir yapıda
böyle bir ayrım yoktur, çünkü bir yapının bütün alanları her zaman aynı andadır — Nokta
değeri hiçbir zaman “şu an yalnızca xi var” durumuna düşmez. enum bunu tersine çevirir:
bir değer, aynı anda yalnızca bir varyantın alanlarına sahiptir, diğer varyantların
alanları o değer için hiç yoktur.
Bu, Sekil::Dikdortgen(3.0, 4.0) değerinin aynı zamanda bir yaricap alanı da taşıdığı
anlamına gelmez — o değer yalnızca genislik ve yukseklik taşır, Cember varyantının
alanı ondan tamamen ayrıdır. alan fonksiyonunun match ifadesindeki her dal, yalnızca
kendi varyantının alanlarına erişebilir; Sekil::Cember(yaricap) => ... dalında genislik
adına erişmek mümkün değildir, çünkü o dal yalnızca Cember varyantı eşleştiğinde
çalışır ve o anda ortada bir genislik yoktur.
Bu, bellekte kaç baytın ayrıldığı sorusuna değinmez — ders böyle bir sayı basmıyor —
yalnızca hangi alanların hangi durumda erişilebilir olduğunu ölçüyor. Erişilebilirlik,
matchin dal yapısıyla dile getirilen bir kaynak-düzeyi kuraldır: bir dalın içine yalnızca
o dalın eşleştiği varyantın alanları girer.
Sınırlayıcı Ölçüm: Varyant Sayısı Durum Sayısı Değildir
Sekil üç varyant taşıyor: Bos, Cember, Dikdortgen. Ama “üç varyant” ile “üç olası
değer” aynı şey değildir. Bosun taşıyabileceği tek bir durum vardır — kendisi. Cember
ise bir f64 taşıyor ve bir f64ün alabileceği değer sayısı sayılamayacak kadar
büyüktür. Aynı varyant, farklı yarıçaplarla, sonsuz sayıda farklı durum üretebilir.
// ayni_varyant_farkli_durum.rs — varyant sayisi durum sayisi degildir
enum Sekil {
Cember(f64),
}
fn main() {
let kucuk = Sekil::Cember(2.0);
let buyuk = Sekil::Cember(5.0);
let Sekil::Cember(r1) = &kucuk;
let Sekil::Cember(r2) = &buyuk;
println!("{:<24}: {}", "kucuk cember yaricapi", r1);
println!("{:<24}: {}", "buyuk cember yaricapi", r2);
println!("{:<24}: {}", "ayni varyant mi", true);
println!("{:<24}: {}", "ayni durum mu", r1 == r2);
}
kucuk cember yaricapi : 2 buyuk cember yaricapi : 5 ayni varyant mi : true ayni durum mu : false
kucuk ile buyuk, ikisi de Cember varyantını taşıyor — “ayni varyant mi” sorusunun
yanıtı true. Ama ikisi aynı durumu taşımıyor: biri 2.0 yarıçaplı, öbürü 5.0 yarıçaplı
— “ayni durum mu” sorusunun yanıtı false. Bir önceki koşumda ölçülen “üç ayrı varyant”
sayısı, Cember gibi veri taşıyan bir varyantın kaç farklı durumda görünebileceğini
söylemiyor.
RV7. — Bu sınırlayıcı ölçüm, tek bir kod bloğunda iki ayrı Sekil::Cember değeri
kurup ikisinin de aynı varyanttan geldiğini (true) ama farklı bir yarıçap taşıdığını
(r1 == r2 sonucu false) yan yana basarak ölçülüyor. “Varyant sayısı” kaynaktan
enum bildirimini okuyarak (üç satır, üç varyant) bulunabilirken, “durum sayısı” ancak
programın çalışması sırasında hangi değerlerin üretildiğine bakılarak bulunabilir; ikisi
farklı sorulara yanıt veriyor.
Bu ayrım, veri taşımayan bir varyant için ortadan kalkar. Bosun tek bir durumu vardır,
çünkü hiçbir alan taşımaz — varyant sayısı ile durum sayısı orada çakışır. Fark yalnızca
veri taşıyan varyantlarda açılır ve açıldığı andan itibaren durum sayısı taşınan verinin
tipine bağlı hâle gelir: bir bool taşıyan bir varyantın iki durumu olur, bir f64
taşıyan bir varyantın ise pratikte sınırsız durumu olur.
Bu, tek alanlı bir varyant için bile geçerlidir; alan sayısı arttıkça durum sayısı çarpımsal
büyür. Dikdortgen(f64, f64) iki ayrı f64 taşıyor — genişlik ve yükseklik birbirinden
bağımsız değişebiliyor, bu yüzden bu tek varyantın durum uzayı, bir boyutlu değil iki
boyutlu bir sonsuzluktur. Yukarıdaki koşumda yalnızca Dikdortgen(3.0, 4.0) görüldü, ama
kaynakta yazan Dikdortgen(f64, f64) bildirimi bundan çok daha fazlasına izin veriyor:
Dikdortgen(1.0, 1.0), Dikdortgen(0.5, 100.0) ve sayılamayacak kadar başka çift, hepsi
aynı varyanta ait ama hiçbiri aynı durumu paylaşmıyor.
Bu yüzden “kaç durum” sorusunun yanıtı, enum bildirimine bakılarak değil, o bildirimin
alanlarındaki tiplere bakılarak verilir. Veri taşımayan bir varyant durum sayısına tam
olarak 1 katar; sabit sayıda ayrı değer alabilen bir tip taşıyan bir varyant (örneğin
kendisi de sınırlı sayıda varyantı olan başka bir enum) o tipin durum sayısı kadar katkı
yapar; f64 ya da String gibi sınırsız bir tip taşıyan bir varyant ise durum sayısını
sınırsız yapar. Kaynaktaki üç satırlık enum Sekil { ... } bildirimi bu farkı hiçbir
yerde göstermiyor — üçü de aynı biçimde, birer varyant adı olarak yazılıyor.
Bu ayrımın pratik bir sonucu var: bir Sekil değerini ele alan bir kodun doğruluğu,
“üç varyantın hepsi ele alındı mı” sorusuyla ölçülür — “sonsuz durumun hepsi test edildi
mi” sorusuyla değil. alan fonksiyonu Cember(2.0) için doğru sonucu veriyorsa,
Cember(5.0) için de aynı yoldan doğru sonucu verir, çünkü ikisi aynı dalda işleniyor;
tek tek her yarıçapı sınamaya gerek yoktur. Derleyicinin bütünlük denetimi de tam olarak
varyant düzeyinde çalışır, durum düzeyinde değil — bu sınır sıradaki dersin ölçtüğü
“bütünlük” kavramının neye garanti verip neye vermediğini de önceden belirliyor.
Bu ders, bir Sekil değerinin kaç ayrı biçim alabileceğini ve bu biçimlerin kaynakta nasıl
kurulduğunu ölçtü. Ama bir değeri kurmak bir yarısı; kurulan değerin hangi varyantı
taşıdığını okumak öbür yarısıdır ve bu ders o okumayı yalnızca iki yardımcı
fonksiyonun (varyant_adi, alan) içinde, arka planda kullandı. matchin kendisi —
hangi desen biçimlerinin var olduğu, bir dalın ne zaman diğerinden önce geldiği, derleyicinin
neden bütün varyantların ele alınmasını istediği — hiç açılmadı.
Özet
enumbildirimi bir öğedir: çalıştırıldığında hiçbir değer üretmez, yalnızca varyantları tanımlar.- Bir varyant kurmak (
Sekil::Cember(2.0)gibi) bir ifadedir ve bir değer üretir. - Veri taşıyan ve taşımayan varyantlar aynı
enumtipi altında eşit ağırlıkta sayılır; bu sayede tek bir koleksiyon (Vec<Sekil>gibi) hepsini birlikte tutabilir. - Varyant sayısı, kaynaktaki
enumbildiriminden okunan sabit bir sayıdır. - Durum sayısı bundan farklıdır: veri taşıyan bir varyant, taşıdığı verinin tipine bağlı olarak bir, birkaç ya da sayılamayacak kadar çok durum üretebilir.
Sonraki Adım
Bu ders, bir Sekil değerinin kaç ayrı varyant taşıyabileceğini ve varyant kurmanın nasıl
bir ifade olduğunu ölçtü; varyant_adi ve alan fonksiyonları bu varyantları ayırt etmek
için match kullandı ama matchin kendi kurallarına girmedi. Sıradaki ders tam bu
boşluğu dolduruyor: bir Sekil değerinin hangi varyantı taşıdığı kaynakta nasıl okunuyor,
kaç ayrı desen biçimi bunu yapabiliyor ve derleyici bütün varyantların ele alınmasını
neden istiyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.