3 puan yazan GN⁺ 2026-02-23 | 1 yorum | WhatsApp'ta paylaş
  • 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

 
GN⁺ 2026-02-23
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 newtype kalıbının tam anlamıyla “correct by construction” garanti etmediği belirtiliyor
    Tip 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

    • newtype tabanlı “parse, don’t validate” da pratikte oldukça faydalıdır
      Bir 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..100 ile aralık sınırı koyabilir veya [T] is [_, ..] ile boş olmayan slice ifade edebilirsiniz
      Ancak (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ır
    • Nihai hedef ‘correct by construction’dır
      NonZeroU32 gibi tipler basittir, ama asıl güç tüm alan mantığını tiplerle tasarlayıp derleyiciyi bekçi haline getirmekte yatar
      Böylece hata ayıklama yükü çalışma zamanından tasarım aşamasına taşınır
    • İlgili kaynakları “make invalid states impossible/unrepresentable” anahtar sözcükleriyle de bulabilirsiniz
      Örnek olarak "Domain Modeling Made Functional" ve ilgili video bakmaya değer
    • Sıfıra bölme örneği, ilgi alanlarının ayrılması açısından kötü bir örnektir
      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 edilebilir
    Idris’teki Vect n a ve Fin n tipleri buna örnektir

    • Rust’ta da macro tabanlı olarak dependent type’ı taklit eden kütüphaneler var
      Örnek: anodized (tanıtım videosu)
    • Dizi uzunluğu stdin’den okunuyorsa derleme zamanında bilinmez; dolayısıyla bu tür doğrulama yalnızca statik bilginin bulunduğu durumlarla sınırlıdır
    • Bu tür özelliklerin daha yaygın hale gelmesini umuyorum
  • 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ı

    • Perlis’in “tek bir veri yapısı üzerinde 100 fonksiyon” sözü ile “Parse, Don’t Validate” arasında bir gerilim vardır
      Ö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
    • Bu saf bir alternatiften ziyade bir trade-offtur
      Dış girdiler sonuçta yine parse edilmek zorundadır, yani tamamen yerini almaz
    • Bu, “stringly typed language” eleştirisine benziyor gibi gelebilir, ama pratikte veri biçimini kademeli olarak rafine etme sürecidir
    • Denge önemlidir
      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> veya Number<char> tiplerinin aralık dışına çıkınca istisna fırlatması gibi

  • Yazıdaki try_roots örneği aslında bir karşı örnek
    b^2 - 4ac >= 0 kısıtını tip ile ifade etmek Rust’ta çok karmaşık hale gelir
    Böyle durumlarda sadece Option döndürmek ve doğrulamayı fonksiyon içinde yapmak daha mantıklıdır
    Doğrulamaların çoğu birden fazla değerin etkileşimini ele aldığı için, bunu “parsing” ile çözmek kullanışsızdır

    • Girdinin geçerliliği birden fazla argüman arasındaki ilişkiye bağlıysa, sonunda bunu fn(abc: ValidABC) gibi tek bir yapıda birleştirmeniz gerekir
  • Bu 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

    • Go’da da deneniyor, ama pointer’ların aşırı kullanımı ve cebirsel tiplerin yokluğu nedeniyle biraz daha ayrıntılı hale geliyor
  • 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

    • Hangi araçları kullandıkları sorulmuştu
  • 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 getirebiliyor
    C# da ileride yerel DU desteği alırsa çok daha temiz hale gelecektir