1 पॉइंट द्वारा GN⁺ 2 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • assert प्री-कंडीशन, पोस्ट-कंडीशन और invariants को कोड में स्पष्ट करने का साधन है, और जिन constraints को type system से enforce किया जा सकता है, उन्हें language features से व्यक्त करना बेहतर है
  • Zig का std.debug.assert macro नहीं बल्कि एक सामान्य function है, जो unreachable के जरिए असंभव code path को चिन्हित करता है और optimization में भी काम आता है
  • Debug और ReleaseSafe में assert फेल होने पर panic के साथ crash होता है, लेकिन ReleaseFast और ReleaseSmall में यह unchecked illegal behavior बन सकता है और प्रोग्राम गलत तरीके से चल सकता है
  • प्रोडक्शन में assert बंद करने से गलत assumptions को जल्दी पकड़ने का मौका खो जाता है, और बाद में कोड गलत assert पर निर्भर होकर vulnerability तक बन सकता है
  • ReleaseSafe और ReleaseFast में क्या चुनना है यह प्रोग्राम की प्राथमिकताओं पर निर्भर करता है, लेकिन मुख्य बात यह है कि assert को ढककर बंद मत कीजिए, गलत assert को ठीक कीजिए

assert की भूमिका और Zig का डिफ़ॉल्ट व्यवहार

  • assert वह साधन है जिससे कोड में यह व्यक्त किया जाता है कि “यह argument null नहीं हो सकता”, “यह integer odd होना चाहिए” जैसी शर्तें हमेशा सच रहनी चाहिए
    • उदाहरण: assert(my_arg != null);, assert(my_num % 2 != 0);
    • अगर type system से constraint enforce किया जा सकता है, तो assert के बजाय language feature का उपयोग बेहतर है
    • Zig में सामान्य pointer *Foo null नहीं हो सकता, और optional pointer ?*Foo null हो सकता है, लेकिन value access करने से पहले check करना अनिवार्य होता है
  • assert प्री-कंडीशन, पोस्ट-कंडीशन और invariants को स्पष्ट करने के लिए उपयुक्त है
    • अच्छा assert programming mistakes पकड़ने में unit tests से भी ज़्यादा ताकतवर हो सकता है
    • fuzzing के साथ उपयोग करने पर assert का असर और बढ़ सकता है

Zig का unreachable और assert

  • Zig का assert गलत code path को चिन्हित करने वाली language feature unreachable पर आधारित है
    • switch में जिस branch तक पहुँचना असंभव है, उसे .a => unreachable की तरह लिखा जा सकता है
    • unreachable को statement की तरह भी और वहाँ भी इस्तेमाल किया जा सकता है जहाँ किसी भी type की expression चाहिए
    • असंभव स्थिति में कोई अस्थायी value जबरन बनाने की ज़रूरत नहीं पड़ती
  • Zig standard library का std.debug.assert इस तरह implement किया गया है
    pub fn assert(ok: bool) void {
      if (!ok) unreachable; // assertion failure
    }
    
  • unreachable की जानकारी optimization में उपयोग हो सकती है
    • compiler असंभव code path हटा सकता है, और यह जानकारी propagate होकर non-local optimization भी संभव बनाती है
    • हर assert performance improvement नहीं देता, लेकिन ऐसे optimization भी हो सकते हैं जिनकी programmer को आसानी से उम्मीद नहीं होती

build modes और runtime safety

  • Zig में Debug, ReleaseSafe, ReleaseFast, ReleaseSmall build modes होते हैं
    • यह setting ज़रूरी नहीं कि सिर्फ पूरे program पर globally ही लागू हो
    • हर dependency को अलग mode में build किया जा सकता है, और @setRuntimeSafety का उपयोग करके function के अंदर block स्तर पर भी runtime safety बदली जा सकती है
  • assert failure, Zig में “illegal behavior” होता है
    • Checked modes जैसे Debug, ReleaseSafe, @setRuntimeSafety(true) में program panic के साथ crash होता है
    • Unchecked modes जैसे ReleaseFast, ReleaseSmall, @setRuntimeSafety(false) में “unchecked illegal behavior” होता है और program गलत तरह से काम कर सकता है
  • unchecked illegal behavior का परिणाम guaranteed नहीं होता
    • उदाहरण वाले switch में, अभी बनने वाले machine code की विशेषताओं के कारण ऐसा लग सकता है कि वह किसी दूसरी branch में चला जाए
    • compiler के दूसरे versions में बिल्कुल अलग गलत behavior दिखाई दे सकता है
    • संबंधित behavior godbolt उदाहरण में देखा जा सकता है
  • ReleaseSafe और ReleaseFast में assert और उसके बाद आने वाला switch कैसे अलग behave करते हैं, यह एक और godbolt उदाहरण में देखा जा सकता है
    • ReleaseFast में function सभी comparisons छोड़कर true return करता हुआ दिखता है
    • इस तरह का optimization वही है जिस पर video games और दूसरे real-time media applications बहुत हद तक निर्भर करते हैं

Zig assert macro नहीं है

  • Zig का std.debug.assert macro नहीं बल्कि सामान्य function है
    • Zig में macros नहीं हैं
    • C/C++ developers के लिए Zig में यह अक्सर चौंकाने वाली बात होती है
  • C/C++ में assert disable होने पर अक्सर assert call और उसमें दी गई expression पूरी तरह comment-out जैसी हो जाती है
    • इसलिए C/C++ में side effects वाली expressions को assert के अंदर नहीं रखना चाहिए
    • क्योंकि assert disable होने पर वह operation ही गायब हो सकता है
  • Zig में function call rules के अनुसार arguments, function call से पहले evaluate होते हैं
    • std.debug.assert के अंदर की logic से अलग, argument expression evaluate होगी ही
    • इसलिए इस तरह side effects वाली expression भी assert में डाली जा सकती है
    // assert that the remove operation is not a noop:
    assert(my_map.remove("expected-to-exist"));
    
  • दूसरी तरफ, अगर assert condition निकालने के लिए complex computation चाहिए, तो unchecked mode में भी वह computation ज़रूरी नहीं कि हट ही जाए
    • ऐसे मामलों में comptime if से code को guard करना चाहिए
    const builtin = @import("builtin");
    
    if (builtin.mode == .Debug) {
      var condition = ...;
      // whatever bookkeeping is necessary
      // to compute the condition
      assert(condition == .ok);
    }
    
  • C/C++ semantics की आदत हो तो यह अजीब लग सकता है, लेकिन Zig में सामान्य धारणा यही है कि assert को आम तौर पर disable नहीं किया जाता

प्रोडक्शन में assert बंद करने की समस्या

  • assert के साथ मोटे तौर पर तीन विकल्प होते हैं
    • उसे runtime check की तरह बनाए रखें और failure पर process को panic के साथ crash होने दें
    • performance optimization के लिए assert का उपयोग करें, लेकिन assert गलत होने पर program misbehavior स्वीकार करें
    • assert को पूरी तरह disable कर दें
  • std.debug.assert, assert को पूरी तरह disable करने का built-in support नहीं देता
    • अगर आप build-time flag देखने वाला अपना assert implement करें, तो C/C++ जैसा behavior बनाया जा सकता है
  • assert बंद करने की इच्छा आम तौर पर दो कारणों के मेल से आती है
    • performance cost या application crash पसंद नहीं, इसलिए runtime check बनाए नहीं रखना चाहते
    • assert हमेशा सही है इस पर पूरा भरोसा नहीं, इसलिए optimization में उपयोग होने पर संभावित misbehavior से डरते हैं
  • जैसा कि matklad ने संबंधित चर्चा में याद दिलाया, कुछ स्थितियों में crash से बचना एक वैध engineering कारण हो सकता है
    • लेकिन सामान्य software में crash avoidance को default मानना खराब विकल्प माना जाता है
  • assert disable करने पर वह condition भी सचमुच होने पर program चलता रहता है जिसे असंभव माना गया था
    • program गलत assumption के नीचे चलता रहता है, और यह unchecked illegal behavior न भी हो तो misbehavior का एक रूप है
  • unchecked illegal behavior या C के undefined behavior के ख़तरनाक होने का कारण यह है कि ये program को weird machine में बदलने का रास्ता बन सकते हैं
    • पर्याप्त रूप से complex software में UIB के बिना भी program अनचाहे ढंग से मुड़ सकता है
    • runtime पर assert का false होना specification से बाहर की बात है, और अपने आप में program से अनचाहा काम करा सकता है
    • SQL injection इसका ठोस और व्यापक उदाहरण है, जो UIB के बिना भी weird-machine स्तर की गड़बड़ी पैदा कर सकता है
  • अगर program misbehavior की लागत बहुत अधिक है, तो assert को enabled रखना सही है
    • अगर performance बहुत महत्वपूर्ण है और misbehavior का जोखिम स्वीकार्य है, तो assert को optimization opportunity की तरह उपयोग करना सही है
    • assert disable करने से performance भी छूट सकती है और यह झूठा एहसास भी बन सकता है कि system वास्तव में जितना है उससे ज़्यादा सुरक्षित है

गलत assert कोडबेस को कैसे भ्रमित करता है

  • मुख्य जोखिम यह है कि गलत assert tests में सामने न आए और सिर्फ production में fail हो
    • अगर यह guarantee हो कि हर assert हमेशा true है, तो assert को optimization में उपयोग करना विवाद का विषय नहीं होगा
    • अगर यह guarantee हो कि tests हर गलत assert पकड़ लेंगे, तो production optimization भी सुरक्षित होगा
    • लेकिन व्यवहार में गलत assert लिखा जा सकता है, और tests उसे ज़रूरी नहीं कि पकड़ें
  • production में assert बंद करने से गलत assert को जितनी जल्दी हो सके खोजने का मौका खो जाता है
    • इससे भी बड़ी समस्या यह है कि बाद का code उसी गलत assert पर भरोसा करके लिखा जाता रहता है
  • उदाहरण के code में यह assumption assert के रूप में है कि processThing सिर्फ उसी thing पर call होना चाहिए जो पहले से started हो
    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
    
       // ...
    }
    
  • हो सकता है यह assert tests में कभी fail न हो, और production में disable होने के कारण इसका वास्तव में false होना नज़रअंदाज़ हो जाए
    • अगर user-facing misbehavior न दिखे, तो सब कुछ ठीक लग सकता है और development जारी रहती है
  • बाद में कोई यह मानकर code जोड़ सकता है कि thing पहले ही started है, इसलिए अतिरिक्त तैयारी के बिना baz call किया जा सकता है
    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
    
       // ...
    
       // Since thing is already started, we don't
       // need to foo the bar before bazzing the qux.
       // It would be really bad to baz the qux otherwise,
       // so we add an assert for good measure.
       assert(thing.is_fooed);
       thing.baz(qux);
    }
    
  • भले दूसरा assert अपने आप में logically सही हो, अगर पहला assert वास्तव में false हो सकता है तो जोखिम पैदा होता है
    • tests में पहला assert fail नहीं होता, इसलिए दूसरा assert भी fail नहीं होता
    • production में assert disable होने से vulnerability के codebase में घुसने के समय का पता ही नहीं चल सकता
  • अगर code के भीतर के asserts developers को ही गुमराह कर रहे हों, तो सही code लिखना अनुचित रूप से कठिन हो जाता है

विकल्प program की प्राथमिकताओं पर निर्भर करते हैं

  • हर program की प्राथमिकताएँ अलग होती हैं, और कुछ programs में misbehavior risk कम करने से अधिक performance को प्राथमिकता देना उचित हो सकता है
    • ऐसे में assert को optimization opportunity में बदलना स्वाभाविक विकल्प है
  • production में आदतन assert disable करना, assert enabled रखने से भी और performance optimization का सक्रिय उपयोग करने से भी कमतर विकल्प माना गया है
    • ReleaseFast को लेकर बहुत आलोचनात्मक रहते हुए assert disable करना बिना आलोचना स्वीकार करना विरोधाभासी है
  • Zine एक static site generator है, और फिलहाल मुख्य रूप से personal blogs build करने में इस्तेमाल होता है
    • इसका threat model स्पष्ट परिभाषित नहीं है, और वही इसकी सर्वोच्च प्राथमिकता भी नहीं है
    • Hugo से लगभग एक order of magnitude तेज़ चलने की वजह से इसका ReleaseFast build वितरित किया जाता है
  • Awebo pre-alpha चरण का self-hostable Discord alternative है
    • यह पहले से स्पष्ट है कि यह private data संभालेगा और internet-facing software होगा
    • release के समय ReleaseSafe build देने की योजना है
    • हालांकि FFmpeg, Xiph Opus, SQLite जैसी कुछ core dependencies ReleaseFast में build की जाएँगी
    • इन dependencies में performance gains को program misbehavior risk कम करने से स्पष्ट रूप से ज़्यादा महत्वपूर्ण माना गया है

वास्तविक projects के विकल्प और security मामले

Zig में पूरी तरह गायब न होने वाले implicit assert

  • भले custom assert disable किए जा सकें, लेकिन Zig language खुद जो implicit asserts code में जोड़ती है, उन्हें disable नहीं किया जा सकता
    • integer overflow, 0 से division, array bounds से बाहर जाना आदि इसमें आते हैं
    • ऐसी conditions runtime panic करा सकती हैं या optimization के लिए उपयोग हो सकती हैं
  • production में assert disable करने की आदत, गलत asserts को codebase के भीतर सड़ने और बढ़ने देती है
    • नतीजतन UIB को लेकर paranoia बढ़ सकती है, और developers अवचेतन रूप से assert फिर से enable करके परिणाम देखने से डरने लग सकते हैं
  • इससे बचने का अपरिहार्य निष्कर्ष यही है कि assert को disable करके ढकना नहीं चाहिए, बल्कि गलत assert को ठीक करना चाहिए
    • program correctness को किसी छोटे subset नहीं बल्कि पूरे program के लिए साधना चाहिए

1 टिप्पणियां

 
GN⁺ 2 시간 전
Lobste.rs की राय
  • मैं इस बात से सहमत हूँ कि assert पर बस crash कर देना, या Rust के panic की तरह सिर्फ़ काम को crash कर देना, आम तौर पर सबसे बेहतर है। लेकिन यह मानना मुश्किल है कि assert को optimization hint की तरह इस्तेमाल करना, उसे बस हटा देने से हमेशा बेहतर होता है
    पहला, मनमाना assert अक्सर optimization में ज़्यादा मदद नहीं करता, और कई शर्तें ऐसी होती हैं जिन्हें optimizer तुरंत इस्तेमाल भी नहीं कर सकता। जब तक आप “यह branch कभी नहीं चलेगी” जैसी सीधी assumption नहीं डाल रहे, तब तक codebase में इधर-उधर random assumptions बिखेरकर मिलने वाला performance gain बहुत बड़ा होने की संभावना नहीं है
    दूसरा, assert को assumption में बदल देने से गलती का blast radius बहुत बढ़ जाता है। उदाहरण के लिए, किसी ऐसे system में जो project या user के हिसाब से अलग-अलग data process करता है, मान लीजिए किसी calculation function के बीच में ऐसा assert है जो ऐसी state पकड़ता है जो मूलतः असंभव होनी चाहिए। अगर release build में cost ज़्यादा होने के कारण उसे बंद किया जाता है, तो साधारण disable करने की स्थिति में नुकसान एक project या user तक सीमित रह सकता है और बाद की किसी जाँच में पकड़ा भी जा सकता है। लेकिन अगर इसे undefined behavior में बदल दिया जाए, तो calculation किसी अजीब code पर jump कर सकती है, memory को मनमाने ढंग से खराब कर सकती है, और सभी projects का data corrupt कर सकती है
    आखिरकार, अगर release build के default के रूप में unsafe assert चुना जाता है, तो समस्या होने पर नुकसान को localize करने की संभावना कम करने की कीमत पर code के मनमाने हिस्सों को जल्दबाज़ी में optimize किया जा रहा है। मेरे हिसाब से Rust का डिज़ाइन अच्छा है: assert!() हमेशा panic करता है, debug_assert!() सिर्फ debug mode में panic करता है, और assert_unchecked() debug में panic करके release में optimization hint बन जाता है

    • अगर आपको गलती के blast radius की चिंता है, तो ReleaseFast नहीं बल्कि ReleaseSafe इस्तेमाल करना चाहिए
    • मैं अलग-अलग assert बंद करने के ख़िलाफ़ नहीं हूँ, बल्कि एक साथ सबको बंद कर देने के ख़िलाफ़ हूँ, जैसे यह कोई सामान्य अनुशंसित practice हो
      यह मानना बिल्कुल तर्कसंगत है कि performance impact इतना ज़्यादा है कि उन्हें release build में रखा नहीं जा सकता। और जैसा ऊपर कहा, जिन assert की calculation cost ज़्यादा है, उनके performance improvement में बदलने की संभावना भी लगभग नहीं के बराबर है
      Zine में भी ऐसे कुछ उदाहरण हैं:
      https://github.com/kristoff-it/zine/…
      https://github.com/kristoff-it/zine/…
      Zig में कोई “default release mode” नहीं है। assert को कैसे handle करना है यह आपको हमेशा खुद चुनना पड़ता है, और system-wide option crash या optimization में से होता है, जिनमें से कोई भी दूसरे से ज़्यादा default नहीं कहा जा सकता
  • Ghostty में अब तक सार्वजनिक हुई अपेक्षाकृत गंभीर CVE की दोनों घटनाएँ memory corruption के बिना ही arbitrary command execution तक पहुँचीं—यह बात मुझे काफ़ी अजीब लगती है। यह भी कि इसे ReleaseFast में ship किया गया था, दुनिया के काम करने के तरीके को लेकर मेरी समझ से सीधे टकराता है

    • मुझे यह इतना अजीब नहीं लगता। अगर आप उस रिपोर्ट पर भरोसा करें कि गंभीर vulnerabilities में 70% memory-related होती हैं, तब भी वह C और C++ के आधार पर है, और Zig memory safety के मामले में थोड़ा बेहतर हो सकता है। ऊपर से, अगर sample size सिर्फ 2 है, तो लगभग 10 projects में से 1 में ऐसा नतीजा दिखना असामान्य नहीं होगा
      Terminal emulator के साथ काम करने के अनुभव से कहूँ तो ये vulnerabilities बिल्कुल उसी तरह की झंझट वाली समस्याएँ हैं जिनकी उम्मीद की जा सकती है। मैं developers या researchers को नीचा नहीं दिखाना चाहता, लेकिन ऐसी अजीब जगह पर होने वाली command injection इस क्षेत्र में लगभग साथ आने वाली समस्या है, जैसे दूसरे क्षेत्रों में दूसरी तरह की injection vulnerabilities साथ आती हैं
  • यह मज़ेदार है कि production में “performance के लिए” assert और bounds checks बंद करने की दलील मैं लगभग 40 साल से सुन रहा हूँ। इस दौरान computers कई orders of magnitude तेज़ हुए हैं, और software सबकी ज़िंदगी में पहले से कहीं ज़्यादा गहराई तक जा चुका है, इसलिए runtime correctness आज पहले से कहीं अधिक महत्वपूर्ण है
    और ज़्यादा उपयोगी चर्चा की जाए तो, पुराने Microsoft में सामान्य assert, check वगैरह के अलावा एक चीज़ थी जो मैंने और कहीं ज़्यादा नहीं देखी: reporting assert। इसका इस्तेमाल तब होता है जब कोई ऐसी condition हो जिस पर आपका पूरा control न हो, आप उसे true मानकर चलते हों लेकिन false होने पर defensively handle भी करते हों, और साथ ही logs या telemetry के ज़रिए जानना चाहते हों कि क्या वह असल दुनिया में false हो रही है। उदाहरण के लिए, आप मानते हैं कि user किसी list में 1000 से ज़्यादा items नहीं डालेगा इसलिए quadratic algorithm इस्तेमाल करते हैं, या यह मानते हैं कि network latency 200ms से कम होगी इसलिए ऐसा protocol इस्तेमाल करते हैं जिसमें बहुत सारे round trips हों

    • वह check से कैसे अलग है?
  • यहाँ linked लोगों में से एक होने के नाते, मुझे लगता है कि यह assert पर मेरे विचारों को एक हास्यास्पद false dichotomy और caricature में बदल देता है। जैसा मैंने दूसरे comment में भी लिखा, मैं यह तय करना पसंद करता हूँ कि undefined behavior में बदलना है या नहीं, यह हर assert के आधार पर तय हो। ReleaseFast पर मेरी आलोचना यह है कि वह इस choice को सिर्फ किसी दायरे के सभी assert के साथ ही नहीं, बल्कि सभी safety checks के साथ भी बाँध देता है
    मैं kristoff की इस बात से सहमत हूँ कि सिर्फ इसलिए assert बंद कर देना कि ठीक न किया गया assert crash करेगा, मूर्खता है। लेकिन मैं इस बात से सहमत नहीं हूँ कि “crash या undefined behavior” ही एकमात्र तर्कसंगत विकल्प हैं। sibling comment में goldstein की बात मेरी सोच के ज़्यादा क़रीब है

  • assert_unchecked() के behavior को global default बना देना बचाव करना मुश्किल है, लेकिन performance optimization technique के रूप में यह तर्कसंगत हो सकता है। अगर सभी assert को assumptions में बदल देने से production build काफ़ी तेज़ हो जाती है, तो संभव है कि performance improvement का ज़्यादातर हिस्सा पैदा करने वाली कुछ ही assumptions, शायद सिर्फ एक assumption, मौजूद हो, और उसे binary search जैसे तरीके से ढूँढा जा सके

    • कोई default नहीं है; user को स्पष्ट रूप से ReleaseSafe और ReleaseFast/ReleaseSmall में से एक चुनना होता है
  • प्रोग्राम विश्लेषण के साहित्य में कोड के भीतर के assertions या assert को दो रूपों में बाँटने की द्वैतता है। एक है कोड के आसपास का संदर्भ; फ़ंक्शन के मामले में वह शर्त जिसे caller को पूरा करना चाहिए। दूसरा है खुद कोड; फ़ंक्शन के मामले में वह शर्त जिसे फ़ंक्शन को पूरा करना चाहिए.
    यह भेद contract और gradual typing के साहित्य में मानक अकादमिक अवधारणा “blame” से देखने पर स्पष्ट हो जाता है। अगर संदर्भ के बारे में assertion विफल होता है, तो गलती हमारी नहीं बल्कि संदर्भ या caller की होती है, हालांकि caller सही हो और assertion खुद bug हो, यह भी संभव है। अगर खुद कोड के बारे में assertion विफल होता है, तो ज़िम्मेदारी हमारी है, हालांकि कोड सही हो और assertion खुद bug हो, यह भी संभव है।
    फ़ंक्शन स्तर पर precondition संदर्भ के बारे में assertion है, और postcondition कोड के बारे में assertion है। हालांकि दोनों को कोड के बीच में भी रखा जा सकता है। कुछ verification frameworks assert का उपयोग कोड के बारे में assertions के लिए और assume का उपयोग संदर्भ के बारे में assertions के लिए करते हैं। यह इस बात से भी जुड़ता है कि कुछ testing frameworks, खासकर random testing frameworks, इन्हें कैसे समझते हैं। assert विफल होने पर test को failed के रूप में चिह्नित किया जाता है, और assume विफल होने पर test को skip किया जाता है.

    • BIND9 macro REQUIRE() के साथ caller द्वारा पूरी की जाने वाली preconditions की जाँच करता है, और ENSURE() के साथ फ़ंक्शन द्वारा गारंटीकृत postconditions की, यानी यह Design by contract के काफ़ी क़रीब शैली अपनाता है। बीच के checks के लिए INSIST(), और loops या data structures के लिए INVARIANT() भी है। फ़ंक्शन documentation में preconditions और postconditions के अनुरूप “requires” और “ensures” नोट्स होने चाहिए.
  • यह Bun की ओर इशारा करता हुआ लगता है, इसलिए मैं इस कड़ी को थोड़ा और औपचारिक रूप से दर्ज करना चाहता हूँ। Bun के निर्माता Jarred Sumner ने 2024 में एक Zig issue में प्रस्ताव रखा था कि unreachable को ReleaseFast में panic करना चाहिए। उस thread में Andrew Kelley और Matthew Lugg की टिप्पणियाँ इस चर्चा से संबंधित हैं.
    => https://github.com/ziglang/zig/issues/19664
    Bun अपने खुद के assert functions इस्तेमाल करता है, जो release mode में panic करते हैं या हटा दिए जाते हैं, लेकिन undefined behavior नहीं लाते। हालांकि Loris की footnote भी याद रखनी चाहिए: “भाषा के रूप में Zig कोड में बहुत सारे ऐसे assert को अप्रत्यक्ष रूप से जोड़ देता है जिन्हें disable नहीं किया जा सकता।”
    मैं Bun की बात बहुत लंबी नहीं करना चाहता। आखिर वह एक छोटे team का single project है। मुख्य बात यह है कि अगर ज़रा भी चिंता हो तो ReleaseSafe इस्तेमाल करें। ReleaseSafe की छवि धीमे होने की है, लेकिन मेरे छोटे Zig projects में मैं ReleaseSafe और ReleaseFast के बीच benchmark का अंतर माप नहीं पाया। फिर भी इसके बहुत-सी दूसरी भाषाओं से तेज़ होने की संभावना है.

    • अगर ज़रा भी चिंता हो तो ReleaseSafe इस्तेमाल करना सही है। लेकिन इससे भी दिलचस्प रणनीति संभव है। कोड बदलते समय, यानी जब bugs डालने की संभावना हो, तब ReleaseSafe रखें, और जब कोड स्थिर हो जाए और वास्तविक उपयोग में verify हो जाए, तब अगर performance gain की कीमत वाजिब लगे तो ReleaseFast पर स्विच किया जा सकता है।
      या, अगर संदर्भ में उचित हो, तो ReleaseFast executable deploy करने के बाद undefined behavior की वजह से non-deterministic bug reports आने लगें, तब ReleaseSafe पर वापस लौटा जा सकता है। तब आप यह भी जान पाएँगे कि कौन-सा assert fail हुआ, और out-of-bounds access या overflow जैसी चीज़ों सहित actionable bug reports इकट्ठी करके कोड ठीक कर सकेंगे। मैं तो इस तरीके की सिफ़ारिश तब भी करूँगा जब शुरुआत में ReleaseFast deploy न करना चाहिए था लेकिन फिर भी ऐसा फ़ैसला लिया गया हो :^)
      dependencies को समायोजित करके और @setRuntimeSafety का इस्तेमाल करके project के सिर्फ़ कुछ हिस्सों पर भी यही तरीका लागू किया जा सकता है। आख़िर में, अगर समझदारी से काम करने की इच्छा हो तो ज़रूरी tools सब मौजूद हैं.
  • यह नहीं लिखना चाहिए मानो assert calls के अंदर side effects वाले expressions डालना ठीक हो। यह बुरी प्रथा है। error checking के लिए assert का उपयोग करने से भी बचना चाहिए। निष्पक्ष रूप से कहें तो ऐसा नहीं लगता कि लेखक यही कह रहा है।
    दूसरी ओर, यह भी बताया जाता है कि अगर assert किसी जटिल computation पर निर्भर हो, तो unchecked mode में भी वह computation ज़रूरी नहीं कि हट जाए, इसलिए उसे comptime if से guard करना चाहिए।
    उम्मीद है लेखक ने “macro द्वारा छोड़े गए trauma को त्यागकर simplicity को अपनाने का अच्छा अवसर” वाली बात की विडंबना नहीं छोड़ी होगी। बात दरअसल यह है कि “ऐसी simplicity को स्वीकार करो जिसमें program के build mode को ध्यान में रखना पड़े और रक्षात्मक comptime if जगह-जगह छिड़कने पड़ें।”

    • यह बुरी प्रथा क्यों है?
  • मैंने C# में कुछ numerical computation code लिखा है, और release में बंद हो जाने वाले assert का बहुत इस्तेमाल करता हूँ। हर घने loop में चलाने के लिए यह बहुत महँगा है, लेकिन unit tests में यह उपयोगी है कि routine पहली बार NaN input देखते ही तुरंत फट जाए।
    ऐसे NaN अक्सर user input से नहीं आते, बल्कि code bug की वजह से आते हैं, जैसे optimizer का वहाँ पहुँच जाना जहाँ उसे नहीं जाना चाहिए, और इससे बेहतर boundary constraints की ज़रूरत होती है। बेशक user input को sanitize करने की ज़रूरत हो सकती है, लेकिन वह algorithm की गहराई में नहीं बल्कि सबसे बाहरी boundary पर किया जाना चाहिए। अच्छा होता अगर user input sanitization के नतीजे के आधार पर algorithm के अंदरूनी invariants को assert के बिना सिद्ध करने वाला कोई proof system होता, लेकिन यह एक side project है और इसके फटने से कोई मरेगा नहीं.

  • assert को लेकर 90% असहमति इसलिए पैदा होती है क्योंकि उस शब्द की परिभाषा कमज़ोर और कई तरह की है, और इससे सोच और संचार दोनों धुँधले हो जाते हैं। इसलिए इन अवधारणाओं को नीचे दिए गए तीन नामों में बाँटकर सख़्ती से इस्तेमाल करना चाहिए।
    assert(bool) या Rust में assert_unchecked() वह है जिसे programmer हमेशा true मानता है, और compiler भी उसे हमेशा true मानकर optimization में इस्तेमाल करता है। पुरानी भाषाओं के checked assert से जुड़ी ध्वनि से बचने के लिए इसे assume() कहना बेहतर हो सकता है।
    check(bool) में अगर शर्त false हो तो panic होता है और true हो तो execution जारी रहती है, और यह हमेशा ऐसे ही काम करता है।
    debug_check(bool) debug mode में check() जैसा होता है और release mode में हमेशा execution जारी रखता है। व्यवहार में इसे --debug_checks flag से नियंत्रित किया जाता है, जो debug mode में default रूप से enabled होता है।
    इसके साथ एक compiler flag --check_asserts भी चाहिए, जो assert() को check() में बदल दे। यह तब उपयोगी है जब आप अपने assert पर शक कर रहे हों और उन्हें verify करना चाहते हों, और debug mode में यह default रूप से enabled होना चाहिए। जब तक “assert” कहते समय यह बिल्कुल स्पष्ट न हो कि उसका मतलब क्या है, तब तक परिपक्व चर्चा संभव नहीं है और सिर्फ़ शब्द ही बर्बाद होते हैं.