Zero Tarzında Tagged Union'lar
choice, değeri birkaç isimlendirilmiş variant'tan biri olan ve her variant'ın kendi payload'ını taşıdığı bir tipi bildirir:
choice Result {
ok: i32,
err: String,
}
Bir Result değeri ya bir i32 taşıyan bir ok ya da bir String taşıyan bir err'dir. Asla ikisi birden, asla hiçbiri değil. Type system bu garantiyi verir ve match üzerine işlemi rahatlatır.
Bu, diğer dillerin "tagged union", "sum type", "discriminated union" ya da "algebraic data type" dediği fikrin aynısı. Zero bunu choice olarak adlandırır ve grameri küçük tutar.
Bir Choice Tanımlamak
choice Name {
variantA: PayloadTypeA,
variantB: PayloadTypeB,
}
Her satır bir variant listeler. Ad sizindir; iki nokta üst üstesinden sonraki tip, o variant'ın taşıdığı payload'dır. Payload'a ihtiyaç duymayan bir variant Void kullanır:
choice Token {
word: String,
number: i32,
eof: Void,
}
Token.eof, kullanışlı payload'ı olmayan (payload tipi Void) bir variant — terminator tarzı case'ler için faydalı.
Bir Choice Değeri Oluşturmak
Tipi, ardından variant'ı adlandırıp payload'ı geçirerek bir değer inşa edin:
let success = Result.ok(42)
let failure = Result.err("validation failed")
Payload tipi, variant'ın tanımlanan payload'ıyla eşleşmek zorundadır. Result.ok("hello") bir compile error olur çünkü ok bir i32 bekler.
Tip çıkarımı burada da çalışır. Sağ taraf tipi tamamen sabitliyorsa let success = Result.ok(42) yazabilirsiniz ve bağlamanın tipi Result'tır. Bağlama yerinde tipi belgelemek için explicit annotate etmek de iyidir:
let success: Result = Result.ok(42)
Bir Choice'ı Match'lemek
match, bir choice değerini okuma yolunuzdur. Şekil:
match value {
.variantA => binding { /* value variantA olduğunda gövde, payload `binding`'de */ }
.variantB => binding { /* value variantB olduğunda gövde */ }
}
Resmi Zero repo'sundan işlenmiş bir örnek — .ok kolunun çalıştığını görmek için Run'a tıklayın:
match'i tam anlamıyla okuyun: "result'ın hangi variant'ı tuttuğuna göre eşleşen kolu çalıştır ve payload'ı seçilen ada bağla." .ok kolunda value, i32 payload'ıdır. .err kolunda message, String payload'ıdır. Her kol ayrı bir scope'tur; bağlama yalnızca kendi gövdesinin içinde görünür.
Exhaustiveness
Bu, match'in if/else if zincirlerine göre büyük kazanımıdır: compiler her variant'ın bir kola sahip olduğunu doğrular. .err case'ini unutursanız bir varsayılan dala runtime fall-through almazsınız — bir compile error alırsınız:
{
"code": "MAT001",
"message": "match is not exhaustive: missing variant 'err'",
"line": 9
}
(Error code illüstratiftir; prensip sözleşmedir.)
Choice'a yeni bir variant ekleyin — örneğin Result.timeout: Void — ve codebase'deki Result üzerindeki her match, yeni case'i ele alana kadar bir compile error olur. Bu bir özveri değil, bir özellik: compiler size yeni case'in tam olarak nerede dikkat ihtiyaç duyduğunu söyler.
Payload'a İhtiyaç Duymadığınızda
Bir variant'ın payload'ı Void ise ya da bu kolda umursamıyorsanız, bağlamayı yok sayabilirsiniz — ama exhaustiveness'i karşılamak için yine kolu yazmak zorundasınız:
match token {
.word => w { /* w'yi kullan */ }
.number => n { /* n'yi kullan */ }
.eof => _ { /* bağlanacak hiçbir şey yok */ }
}
"Payload'ı yok say" için kesin yazım pre-1.0 Zero'da gelişebilir (ya _ görürsünüz ya da bağlamayı tamamen atlarsınız). Kavramsal nokta — her variant payload olsun olmasın bir kol alır — kararlı kısımdır.
Yaygın Desenler
Result tarzı bir error tipi
Resmi repo'nun kullandığı örneğin tam olarak aynısı:
choice Result {
ok: i32,
err: String,
}
Değerle başarılı olabilen ya da mesajla başarısız olabilen fonksiyonlar bir Result döndürür. Çağıranlar değeri veya mesajı çıkarmak için pattern match yapar. Zero'nun raises/check sistemi başarısızlığa açık işlemlerin yansıtılmasını ele alır; Result, bir başarı-ya-da-başarısızlık değerini veri olarak tutmak istediğinizde faydalıdır.
Bir parser token'ı
choice Token {
word: String,
number: i32,
eof: Void,
}
Bir tokenizer bir Token stream'i üretir. Her tüketici, ne yapacağına karar vermek için variant üzerinde match yapar — kelimeyi yazdır, sayıyı topla, eof'ta çık.
Bir state machine
choice State {
waiting: Void,
processing: i32,
done: String,
}
processing mevcut görev ID'sini taşır; done nihai sonucu taşır. Her geçiş yeni bir State değeridir — bir shape'in içine serpilmiş mutable alanlar yok.
Choice + Generics
choice, shape gibi generic olabilir:
choice Maybe<T> {
some: T,
none: Void,
}
Maybe<i32> "opsiyonel bir integer"dır. Maybe<String> "opsiyonel bir string"dir. Aynı desen Zero standart kütüphanesinde gösterilir ve bir null sentinel değerinden çok daha iyi uyar — tipe karşı match yaptığınızda .none case'ini unutmanız mümkün değildir.
Choice ile Shape ile Enum Arasında Ne Zaman Seçim Yapılır
Shape'ler ve enum'lardan hızlı bir özet:
- Shape — birden fazla alana sahip bir record, hepsi birlikte mevcut.
- Enum — N etiketten biri, ekstra veri yok.
- Choice — N variant'tan biri, her biri bir payload taşıyor.
Gerçek bir programdaki çoğu veri modeli bu üçünün bir kombinasyonudur. "Bu ve mi, veya mu, yoksa veriyle veya mı?" sorusundan başlamanın berraklığı, küçük bir dilde çalışmanın hafife alınan faydalarından biridir.
Sırada: World Capability
choice ve match, Zero'nun veri tarafını kapsar. Bir sonraki bölüm effect'ler hakkında — Zero programlarının dış dünyayla nasıl etkileşime girdiği. Her I/O parçasının kapısını tutan obje olan World capability ile başlıyor.
Sıkça Sorulan Sorular
Zero'da choice nedir?
Bir choice, Zero'nun tagged-union tipidir — birkaç isimlendirilmiş variant'tan biri olan ve her variant'ın kendi payload tipini taşıdığı bir değer. Örnek: choice Result { ok: i32, err: String }. Bir Result değeri ya bir i32 taşıyan bir ok'tur ya da bir String taşıyan bir err. Result.ok(42) veya Result.err("bad") ile oluştururs unuz.
Zero'da match nasıl çalışır?
match value { .variantA => binding { ...body } .variantB => binding { ...body } }, value'nun hangi variant'ı tuttuğuna göre dallanır. Her kol bir variant'ı pattern match eder, payload bağlamasını adlandırır ve gövdesini çalıştırır. Compiler her variant'ı kapsadığınızı doğrular — exhaustiveness, if/else if üzerindeki ana faydadır.
Bir choice değeri nasıl inşa edilir?
Tipi ve variant'ı adlandırıp payload'ı geçirerek oluşturun: let r: Result = Result.ok(42) ya da let r = Result.err("validation failed"). Payload tipi, variant'ın tanımlanan payload'ıyla eşleşmek zorundadır — yanlış tipi geçmek bir compile error'dır.
choice ile enum arasındaki fark nedir?
enum variant'ları payload'sız, yalnızca etikettir. choice variant'larının her biri tanımlanan bir tipte bir değer taşır. Case'lerden birine veri eklemeniz gerekiyorsa (bir hata mesajı, başarılı bir sonuç, parse edilmiş bir token) choice kullanın. Case'ler saf etiketse enum kullanın.
Choice'lar için match neden if-else'e tercih edilir?
match doğası gereği exhaustive'tir — compiler her variant'ın ele alındığını kontrol eder, dolayısıyla sonradan yeni bir variant eklemek tip üzerinde dallanan her yeri güncellemeye zorlar. Bir if/else if zinciri ise sessizce düşer; eksik case'i production'da bir bug olarak ortaya çıkana kadar gizler.