3 पॉइंट द्वारा GN⁺ 2026-02-23 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • टाइप सिस्टम का उपयोग करके runtime validation की जगह compile time पर invariants की गारंटी देने वाली Rust डिज़ाइन पद्धति को समझाया गया है
  • NonZeroF32, NonEmptyVec जैसे नए types (newtype) परिभाषित करके गलत states (0, empty vector आदि) को व्यक्त करना असंभव बनाया जाता है
  • Option या Result से failure लौटाने के बजाय, function arguments में constraints को मजबूत करके errors को पहले ही रोका जाता है
  • String::from_utf8 या serde_json::from_str की तरह parsing के ज़रिए meaningful types में बदलने के उदाहरण दिए गए हैं
  • Illegal states को unrepresentable बनाना और validation को जितना हो सके उतना पहले करना — यह डिज़ाइन सिद्धांत code stability और readability दोनों को बेहतर बनाता है

1. Runtime validation की जगह type से constraints व्यक्त करना

  • divide(a, b) function में 0 से divide करने पर runtime panic होता है
    • Option लौटाकर failure व्यक्त किया जा सकता है, लेकिन यह return type को कमजोर करने जैसा है
  • NonZeroF32 type परिभाषित करके केवल non-zero values को ही बनाया जा सकता है
    • constructor fn new(n: f32) -> Option<NonZeroF32> के रूप में है, और failure पर None लौटता है
    • divide_floats(a: f32, b: NonZeroF32) के रूप में define करने पर runtime validation की ज़रूरत नहीं रहती
  • Validation की ज़िम्मेदारी function के अंदर से caller की तरफ शिफ्ट करके errors को पहले ही हटाया जाता है

2. Duplicate validation हटाना और code को सरल बनाना

  • roots(a, b, c) function में a == 0 validation को Option से handle करने पर caller और function दोनों तरफ duplicate validation होता है
  • NonZeroF32 का उपयोग करने पर validation केवल एक बार किया जाता है, और उसके बाद logic सरल हो जाता है
  • इसी सिद्धांत से NonEmptyVec<T> परिभाषित करके empty vector की अनुमति नहीं दी जाती
    • अगर get_cfg_dirs() NonEmptyVec<PathBuf> लौटाए, तो बाद में main() में अतिरिक्त validation की ज़रूरत नहीं रहती

3. वास्तविक उदाहरण: String और serde_json

  • String अंदरूनी रूप से Vec<u8> का newtype है, और String::from_utf8 validity check करता है
    • उसके बाद इसे UTF-8 guaranteed string के रूप में सुरक्षित रूप से इस्तेमाल किया जा सकता है
  • serde_json का from_str::<Sample> JSON को struct में parse करता है और field presence तथा type consistency को compile time पर guarantee करता है
    • foo, bar fields की मौजूदगी, type match, array length आदि सभी constraints type level पर verify होते हैं

4. Type-driven design के दो सिद्धांत

  • Illegal states को unrepresentable बनाना
    • NonZeroF32 में 0 और NonEmptyVec में empty state को व्यक्त नहीं किया जा सकता
    • साधारण validation function (is_nonzero) अब भी गलत state को व्यक्त कर सकता है, इसलिए वह अधूरा है
  • Validation को जितना संभव हो उतना पहले करना
    • ‘Shotgun Parsing’ की तरह अगर validation पूरे codebase में बिखर जाए, तो यह security vulnerabilities (CVE-2016-0752 आदि) तक ले जा सकता है
    • Parsing stage में सभी constraints check कर लेने पर बाद का logic सुरक्षित रूप से चलाया जा सकता है

5. Rust में type-based proof और उसके उपयोग

  • Curry-Howard correspondence के अनुसार types को logical propositions और values को उनके proofs के रूप में देखा जा सकता है
    • typenum crate का उपयोग करके compile time पर mathematical relations (3 + 4 = 8) को verify किया जा सकता है
  • Type system के ज़रिए program की correctness को compile stage पर prove किया जा सकता है

6. Practical application के लिए सलाह

  • अगर external API simple types (bool, i32) माँगता हो, तब भी अंदरूनी तौर पर meaningful enum या newtype का उपयोग करें
    • उदाहरण: LightBulbState { On, Off } define करें और From<LightBulbState> for bool implement करें
  • अगर verify() या do_something_fallible() जैसे साधारण validation functions हों, तो parsing के ज़रिए structured type conversion पर विचार करें
  • यदि function का कोई side effect नहीं है, तो Result<Infallible, MyError> की तरह जानबूझकर impossible state को type में व्यक्त किया जा सकता है

7. निष्कर्ष

  • Rust के type system को validation tool की तरह इस्तेमाल करने पर code की clarity और stability बेहतर होती है
  • Vec, sqlx, bon जैसे Rust ecosystem के कई tools पहले से type-based design का उपयोग कर रहे हैं
  • हर समस्या को type से हल नहीं किया जा सकता, लेकिन validation logic को type level तक उठाने का तरीका maintainability और safety दोनों बढ़ाता है
  • Rust के शक्तिशाली type system का अधिकतम उपयोग करके ऐसा code लिखने की सिफारिश की गई है जिसमें compiler errors पकड़ ले

1 टिप्पणियां

 
GN⁺ 2026-02-23
Hacker News की राय
  • इस लेख में इस्तेमाल किया गया 0 से भाग वाला उदाहरण “Parse, Don’t Validate” सिद्धांत को समझाने के लिए उपयुक्त नहीं है
    इस सिद्धांत का मूल भरोसा न किए जा सकने वाले डेटा को संरचनात्मक रूप से सही type में बदलने वाले फ़ंक्शन में है
    Alexis King के "Names are not type safety" लेख में भी कहा गया है कि newtype पैटर्न पूरी तरह का ‘correct by construction’ सुनिश्चित नहीं करता
    जब type system invariants को सीधे व्यक्त नहीं कर सकता, तब smart constructor के साथ parser जैसा व्यवहार करने वाले abstract type का उपयोग एक व्यावहारिक तरीका है
    दूसरा उदाहरण, non-empty vec, कहीं बेहतर है, क्योंकि यह type system के भीतर “हमेशा कम-से-कम एक element मौजूद है” की गारंटी देता है

    • newtype आधारित “parse, don’t validate” भी व्यवहार में बहुत उपयोगी है
      जब यह पता न हो कि string कहाँ से आई है, तो encapsulated value विश्वसनीयता को काफ़ी बढ़ा देता है
      पूरी correctness-by-construction के लिए dependent type system चाहिए, लेकिन Rust के pattern types जैसे हल्के विकल्प भी मौजूद हैं
      उदाहरण के लिए i8 is 0..100 की तरह range सीमित की जा सकती है, या [T] is [_, ..] से non-empty slice व्यक्त किया जा सकता है
      हालांकि (T, Vec<T>) रूप वाली non-empty list व्यावहारिकता और सैद्धांतिक शुद्धता के टकराव को दिखाती है, क्योंकि इसे vector की तरह संभालने में कई सीमाएँ हैं
    • ‘correct by construction’ अंतिम लक्ष्य है
      NonZeroU32 जैसे type सरल हैं, लेकिन असली ताकत पूरे domain logic को types से डिज़ाइन करने में है ताकि compiler gatekeeper की भूमिका निभाए
      ऐसा करने पर debugging का बोझ runtime से हटकर design समय में चला जाता है
    • “make invalid states impossible/unrepresentable” इस keyword से भी संबंधित सामग्री खोजी जा सकती है
      उदाहरण के तौर पर "Domain Modeling Made Functional" और संबंधित वीडियो देखे जा सकते हैं
    • 0 से भाग वाला उदाहरण separation of concerns के गलत उपयोग का मामला है
      इस स्तर पर wrapping करने के बजाय, overflow जैसी arithmetic functions के व्यवहार को wrap करें तो अंतर और साफ़ दिखेगा
  • हाल की संबंधित चर्चा के links संकलित किए गए हैं
    Parse, Don't Validate (2019) (फ़रवरी 2026, 172 comments)
    Parse, Don’t Validate – Some C Safety Tips (जुलाई 2025, 73 comments)
    Parse, Don't Validate (2019) (जुलाई 2024, 102 comments) आदि
    यह सिर्फ़ संदर्भ के लिए साझा किया गया है

  • Parsing over validation दृष्टिकोण की सीमाएँ हैं, खासकर जब वास्तविक दुनिया के सभी मामलों को पहले से जानना संभव न हो
    file format जैसी चीज़ों में जितनी जल्दी हो सके fail करना अच्छा है, लेकिन business logic या state transition modeling में इसे लागू करते समय सावधानी चाहिए
    अगर वास्तविक ज़रूरतें बदलती हैं, तो system उन्हें समायोजित नहीं कर पाता और अंततः उपयोगकर्ता workaround अपनाने लगते हैं

  • दूसरी भाषाओं में dependent typing के साथ इससे आगे जाया जा सकता है
    उदाहरण के लिए get_elem_at_index(array, index) में array की लंबाई पहले से न मालूम हो, तब भी index range को compile time पर सुनिश्चित किया जा सकता है
    Idris के Vect n a और Fin n types इसका उदाहरण हैं

    • Rust में भी macro-आधारित तरीके से dependent types की नकल करने वाली libraries हैं
      उदाहरण: anodized (परिचय वीडियो)
    • अगर array की लंबाई stdin से पढ़ी जाती है, तो compile time पर वह ज्ञात नहीं होगी, इसलिए ऐसी validation स्थिर जानकारी उपलब्ध होने वाले मामलों तक सीमित रहती है
    • उम्मीद है कि ऐसी सुविधाएँ और व्यापक होंगी
  • एक type में कई functions रखने वाला तरीका भी है
    यह Clojure की तरह एक map में सारे डेटा को व्यक्त करने और पूरी standard library को उसी पर काम करने देने जैसा है

    • Perlis का “एक data structure पर 100 functions” वाला विचार और “Parse, Don’t Validate” के बीच तनाव मौजूद है
      महत्वपूर्ण invariants को type में डाला जा सकता है, या उन्हें साधारण functions से व्यक्त किया जा सकता है
      dynamic typed languages में भी समान प्रभाव देने वाली design आदतें होती हैं
    • यह कोई शुद्ध विकल्प नहीं, बल्कि एक trade-off है
      बाहरी input को आखिरकार parse करना ही पड़ता है, इसलिए यह उसका पूरा विकल्प नहीं बनता
    • यह “stringly typed language” वाली आलोचना जैसा सुनाई दे सकता है, लेकिन वास्तव में यह डेटा के रूप को क्रमिक रूप से परिष्कृत करने की प्रक्रिया है
    • संतुलन महत्वपूर्ण है
      structural type system में branding के ज़रिए nominal type की नकल की जा सकती है, और उल्टा भी, लेकिन यह ergonomically बहुत अच्छा नहीं होता
      अंततः दोनों तरीकों को उचित रूप से मिलाकर चलना ही व्यावहारिक है
  • यह चर्चा C++ के concepts फीचर की याद दिलाती है
    Bjarne Stroustrup के Concept-based Generic Programming में integer conversion को अपने-आप validate करने का उदाहरण दिखाया गया है
    Number<unsigned int> या Number<char> type range से बाहर होने पर exception फेंकते हैं

  • लेख का try_roots उदाहरण वास्तव में counterexample है
    b^2 - 4ac >= 0 जैसी constraint को type से व्यक्त करना Rust में बहुत जटिल हो जाता है
    ऐसे मामलों में बस Option लौटाना और function के भीतर validate करना अधिक उचित है
    ज़्यादातर validation कई values की परस्पर क्रिया से जुड़ी होती है, इसलिए उसे “parsing” से हल करना असुविधाजनक हो सकता है

    • जब input की वैधता कई arguments के बीच संबंध पर निर्भर करती है, तब अंततः उसे fn(abc: ValidABC) जैसी एकीकृत रूपरेखा में बाँधना पड़ता है
  • यह पैटर्न API design में भी बहुत अच्छी तरह फिट बैठता है
    JSON request को validate करने के बजाय, शुरुआत में ही उसे type-सुनिश्चित struct में parse कर लिया जाए तो आगे के logic में duplicate validation की ज़रूरत नहीं रहती
    Rust में serde + custom deserializer के संयोजन से यह आसानी से किया जा सकता है
    वास्तव में, ऐसे तरीके से error handling code 60% कम होने का उदाहरण देखा गया है

    • Go में भी यह कोशिश की जाती है, लेकिन pointer के अत्यधिक उपयोग और algebraic types की अनुपस्थिति के कारण यह कुछ verbose हो जाता है
  • यही दर्शन UI design system पर भी लागू किया गया है
    CSS को बाद में जाँचने के बजाय, ऐसे types परिभाषित किए जाते हैं जिनसे केवल grid units में placement संभव हो, ताकि 13px जैसे मनमाने margin compile error बन जाएँ
    इससे design deterministic बना रहता है

    • किसी ने पूछा कि इसके लिए कौन-सी tooling इस्तेमाल की जाती है
  • C# के records + pattern matching इस approach के काफ़ी करीब हैं
    F# के discriminated unions इससे भी अधिक शक्तिशाली हैं, क्योंकि Result<'T,'Error> के ज़रिए गलत states को व्यक्त करना असंभव बनाया जा सकता है
    C# में भी भविष्य में native DU आने पर यह काफ़ी अधिक साफ़-सुथरा हो जाएगा