- Rust’ta çalışma zamanı doğrulaması yerine değişmezleri derleme zamanında garanti altına almak için tip sisteminden yararlanan bir tasarım yaklaşımı açıklanıyor
NonZeroF32, NonEmptyVec gibi yeni tipler (newtype) tanımlanarak hatalı durumların (0, boş vektör vb.) ifade edilmesi imkânsız hale getiriliyor
- Başarısızlığı
Option ya da Result ile döndürmek yerine, fonksiyon parametrelerindeki kısıtlar güçlendirilerek hatalar en baştan engelleniyor
String::from_utf8 veya serde_json::from_str gibi örneklerle, parse etme yoluyla anlamlı tiplere dönüştürme yaklaşımı gösteriliyor
- Geçersiz durumları ifade edilemez kılma ve doğrulamayı mümkün olduğunca erkene çekme ilkeleri, kodun güvenilirliğini ve okunabilirliğini artırıyor
1. Çalışma zamanı doğrulaması yerine kısıtları tiplerle ifade etmek
divide(a, b) fonksiyonunda 0’a bölme bir çalışma zamanı panic’ine yol açar
- Başarısızlığı göstermek için
Option döndürülebilir, ancak bu dönüş tipini zayıflatan bir yaklaşımdır
NonZeroF32 tipi tanımlanarak yalnızca 0 olmayan değerlerin oluşturulması sağlanır
- Yapıcı
fn new(n: f32) -> Option<NonZeroF32> biçimindedir ve başarısızlıkta None döndürür
divide_floats(a: f32, b: NonZeroF32) olarak tanımlandığında çalışma zamanında ek doğrulama gerekmez
- Doğrulama sorumluluğu fonksiyonun içinden çağırana taşınarak, hatalar daha en başta ortadan kaldırılır
2. Tekrarlayan doğrulamayı kaldırma ve kodu sadeleştirme
roots(a, b, c) fonksiyonunda a == 0 kontrolünü Option ile ele almak, hem çağıran tarafta hem fonksiyonun içinde yinelenen doğrulamalara neden olur
NonZeroF32 kullanıldığında doğrulama yalnızca bir kez yapılır ve sonrasındaki mantık sadeleşir
- Aynı ilkeyle
NonEmptyVec<T> tanımlanarak boş vektörlere izin verilmez
get_cfg_dirs() fonksiyonu NonEmptyVec<PathBuf> döndürürse, sonrasında main() içinde ek doğrulama gerekmez
3. Gerçek örnekler: String ve serde_json
String, iç yapıda Vec<u8> için bir yeni tip (newtype) olup, String::from_utf8 geçerlilik kontrolünü yapar
- Bundan sonra güvenle UTF-8 garantili bir dize olarak kullanılabilir
serde_json içindeki from_str::<Sample>, JSON’u bir yapıya parse ederek alanların varlığını ve tip tutarlılığını derleme zamanında garanti altına alır
foo, bar alanlarının varlığı, tip eşleşmesi, dizi uzunluğu gibi tüm kısıtlar tip düzeyinde doğrulanır
4. Tip odaklı tasarımın iki ilkesi
- Geçersiz durumları ifade edilemez hale getirmek
NonZeroF32 0 değerini, NonEmptyVec ise boş durumu ifade edemez
- Basit doğrulama fonksiyonları (
is_nonzero gibi) hâlâ hatalı durumları ifade edebildiği için eksik kalır
- Doğrulamayı mümkün olduğunca erkene çekmek
- ‘Shotgun Parsing’ örneğinde olduğu gibi doğrulama kod tabanına dağılırsa, bunun sonucu güvenlik açıkları (CVE-2016-0752 vb.) olabilir
- Parse aşamasında tüm kısıtlar doğrulanırsa, sonraki mantık güvenle çalıştırılabilir
5. Rust’ta tip tabanlı ispat ve uygulamalar
- Curry-Howard correspondence uyarınca tipler mantıksal önermeler, değerler ise bunların ispatları olarak görülebilir
typenum crate’i kullanılarak matematiksel ilişkiler (3 + 4 = 8 gibi) derleme zamanında doğrulanabilir
- Tip sistemi kullanılarak programın doğruluğu derleme aşamasında ispatlanabilir
6. Pratik kullanım önerileri
- Harici API’ler basit tipler (
bool, i32) istese bile, uygulama içinde bunları anlamlı enum’lar veya newtype’larla ifade etmek faydalıdır
- Örnek:
LightBulbState { On, Off } tanımlanıp From<LightBulbState> for bool uygulanabilir
verify() ya da do_something_fallible() gibi basit doğrulama fonksiyonları varsa, parse etme yoluyla yapılandırılmış tip dönüşümünü değerlendirin
- Yan etkisiz bir fonksiyonda
Result<Infallible, MyError> gibi yapılarla kasten imkânsız durumlar tip düzeyinde ifade edilebilir
7. Sonuç
- Rust’ın tip sistemini bir doğrulama aracı olarak kullanmak, kodun açıklığını ve güvenilirliğini artırır
Vec, sqlx, bon gibi Rust ekosistemindeki çeşitli araçlar zaten tip tabanlı tasarımdan yararlanıyor
- Her problemi tiplerle çözmek mümkün olmasa da, doğrulama mantığını tip düzeyine yükseltme yaklaşımı bakım kolaylığını ve güvenliği artırır
- Rust’ın güçlü tip sisteminden mümkün olduğunca yararlanıp, derleyicinin hataları yakaladığı kodlar yazmak önerilir
1 yorum
Hacker News yorumları
Bu yazıda kullanılan sıfıra bölme örneği, “Parse, Don’t Validate” ilkesini açıklamak için çok uygun değil
Bu ilkenin özü, güvenilmeyen veriyi yapısal olarak doğru bir tipe dönüştüren fonksiyondadır
Alexis King’in "Names are not type safety" yazısında da
newtypekalıbının tam anlamıyla “correct by construction” garanti etmediği belirtiliyorTip sisteminin değişmezleri doğrudan ifade edemediği durumlarda, ayrık tiplerle smart constructor kullanıp ayrıştırıcıyı taklit etmek daha gerçekçi bir yaklaşımdır
İkinci örnek olan non-empty vec çok daha iyi bir örnek; çünkü tip sistemi içinde “her zaman en az bir öğe vardır” garantisini sağlar
newtypetabanlı “parse, don’t validate” da pratikte oldukça faydalıdırBir string’in nereden geldiği bilinmediğinde, kapsüllenmiş değer güvenilirliği ciddi biçimde artırır
Tam bir correctness-by-construction için dependent type system gerekir, ama Rust’taki pattern types gibi hafif alternatifler de vardır
Örneğin
i8 is 0..100ile aralık sınırı koyabilir veya[T] is [_, ..]ile boş olmayan slice ifade edebilirsinizAncak
(T, Vec<T>)biçimindeki non-empty list, pratiklik ile kuramsal saflık arasındaki çatışmayı gösteren bir örnektir; vector gibi kullanmak için fazla kısıtlayıcıdırNonZeroU32gibi tipler basittir, ama asıl güç tüm alan mantığını tiplerle tasarlayıp derleyiciyi bekçi haline getirmekte yatarBöylece hata ayıklama yükü çalışma zamanından tasarım aşamasına taşınır
Örnek olarak "Domain Modeling Made Functional" ve ilgili video bakmaya değer
Bu seviyede sarmalamaya çalışmak yerine, overflow gibi aritmetik fonksiyon davranışlarını sarmalamak farkı daha net gösterir
Yakın tarihli ilgili tartışma bağlantılarını derledim
Parse, Don't Validate (2019) (Şubat 2026, 172 yorum)
Parse, Don’t Validate – Some C Safety Tips (Temmuz 2025, 73 yorum)
Parse, Don't Validate (2019) (Temmuz 2024, 102 yorum) vb.
Sadece referans olması için paylaşıyorum
Parsing over validation yaklaşımı, gerçek dünyadaki tüm durumlar bilinemediğinde sınırlamalara sahiptir
Dosya formatlarında mümkün olduğunca erken başarısız olmak iyidir, ancak bunu iş mantığına veya durum geçişlerini modellemeye uygularken dikkatli olmak gerekir
Gerçek dünya gereksinimleri değiştiğinde sistem bunu karşılayamaz hale gelir ve sonunda kullanıcılar etrafından dolaşmanın yolunu bulur
Diğer dillerde dependent typing ile daha ileri gidilebilir
Örneğin
get_elem_at_index(array, index)için, dizinin uzunluğu önceden bilinmese bile indeks aralığı derleme zamanında garanti edilebilirIdris’teki
Vect n aveFin ntipleri buna örnektirÖrnek: anodized (tanıtım videosu)
Tek bir tip üzerinde birden fazla fonksiyon bulundurma yaklaşımı da var
Clojure’daki gibi tüm veriyi tek bir map ile ifade edip, tüm standart kütüphanenin bunu işleyebilmesini sağlama yaklaşımı
Önemli değişmezleri tipe gömebilir ya da basit fonksiyonlarla ifade edebilirsiniz
Dinamik tipli dillerde de benzer etkiyi veren tasarım alışkanlıkları vardır
Dış girdiler sonuçta yine parse edilmek zorundadır, yani tamamen yerini almaz
Yapısal tip sistemlerinde branding ile nominal type taklit edilebilir, tersi de mümkündür ama ergonomik değildir
Sonuçta iki yaklaşımı uygun biçimde karıştırmak daha gerçekçidir
Bu tartışma C++’ın concepts özelliğini hatırlatıyor
Bjarne Stroustrup’un Concept-based Generic Programming metninde, tamsayı dönüşümlerini otomatik doğrulayan örnekler gösteriliyor
Number<unsigned int>veyaNumber<char>tiplerinin aralık dışına çıkınca istisna fırlatması gibiYazıdaki
try_rootsörneği aslında bir karşı örnekb^2 - 4ac >= 0kısıtını tip ile ifade etmek Rust’ta çok karmaşık hale gelirBöyle durumlarda sadece
Optiondöndürmek ve doğrulamayı fonksiyon içinde yapmak daha mantıklıdırDoğrulamaların çoğu birden fazla değerin etkileşimini ele aldığı için, bunu “parsing” ile çözmek kullanışsızdır
fn(abc: ValidABC)gibi tek bir yapıda birleştirmeniz gerekirBu kalıp API tasarımı için de çok uygundur
JSON isteğini doğrulamak yerine, baştan tip garantili bir struct’a parse ederseniz sonraki mantıkta tekrarlı doğrulama gerekmez
Rust’ta serde + custom deserializer kombinasyonuyla bunu yapmak kolaydır
Gerçekten de bu yöntemle hata işleme kodunun %60 azaldığı örnekler gördüm
Aynı felsefe UI tasarım sistemlerine de uygulanabiliyor
CSS’yi sonradan denetlemek yerine, yalnızca grid birimleriyle yerleşime izin veren tipler tanımlayıp 13px gibi keyfi margin’leri derleme hatasına çevirebilirsiniz
Böylece tasarım deterministik kalır
C#’ın records + pattern matching yaklaşımı buna oldukça yaklaşıyor
F#’ın discriminated unions özelliği ise daha da güçlü;
Result<'T,'Error>ile geçersiz durumları ifade edilemez hale getirebiliyorC# da ileride yerel DU desteği alırsa çok daha temiz hale gelecektir