İçeriğe geç
academia.sh

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ı π×22≈12,57\pi \times 2^2 \approx 12{,}57, Cember(5.0)in alanı π×52≈78,54\pi \times 5^2 \approx 78{,}54. İ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

  • enum bildirimi 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ı enum tipi 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 enum bildiriminden 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat