2 पॉइंट द्वारा GN⁺ 4 시간 전 | 2 टिप्पणियां | WhatsApp पर शेयर करें
  • मौजूदा implementation की संरचनात्मक खामियों को हल करने के लिए Rust की 3 लाख lines को Zig में दोबारा लिखा गया, और 487 दिनों में feature parity हासिल कर ली गई; इस साल के उत्तरार्ध में पहले औपचारिक numbered release 0.1.0 का लक्ष्य है
  • नया compiler hot code loading और reproducible cross-compilation, pattern matching के अंदर string interpolation, HTTP routing में heap allocation हटाने को support करता है, और Rocci Bird का wasm size भी आधे से कम होकर 31KB रह गया
  • Zig चुनने की मुख्य वजहें build time, granular allocators और data layout control, compiler development के लिए उपयुक्त ecosystem, और memory-unsafe code की जांच थीं; Zig 0.17.0 का incremental build लगभग 4.6 लाख lines को 35ms में फिर से build करता है
  • वास्तविक bugs के वर्गीकरण में Rust compiler में memory corruption के 21 मामले और Zig compiler में 10 मामले थे, लेकिन अधिकांश गलत code generation के कारण थे; Zig compiler की अपनी memory safety errors file name बिगाड़ने वाली use-after-free की 2 घटनाएं थीं
  • Zig pointer-less data structures, parsing के बिना deserialization, और LLVM bitcode serializer reuse के लिए अच्छी तरह fit हुआ, लेकिन tests में automatic memory deallocation, polymorphism, private struct fields, dead code detection, और releases के बीच compatibility में Rust का developer experience बेहतर था

feature parity तक पहुंचा rewrite

  • Roc compiler team ने करीब डेढ़ साल में Rust की 3 लाख lines को Zig में फिर से लिखकर मौजूदा compiler के साथ feature parity हासिल कर ली
  • feature parity एक अहम milestone है, लेकिन यह औपचारिक release नहीं है; नए compiler की 0.1.0 release इस साल के उत्तरार्ध के लिए targeted है
  • 2024 के WASM-4 game Rocci Bird को अब नए compiler से build किया जा सकता है
    • पूरा game Roc code की 1,000 lines से कम में है
    • updated source, original से ज्यादा concise है
    • roc build --opt=size से generated wasm binary 31KB है, जो पुराने compiler के output के आधे से भी कम है
  • homepage पर 2.5MB WebAssembly compiler के साथ browser में basic Roc program लिखा और चलाया जा सकता है
  • rewrite में 487 दिन लगे, जो Bun द्वारा Zig की लगभग 5 लाख lines को Rust में port करने में लगे 11 दिनों से 476 दिन ज्यादा थे
    • Bun ने direct porting की थी, लेकिन Roc का काम कई compiler structures और features को बड़े पैमाने पर बदलने वाले rewrite के ज्यादा करीब था
    • इसलिए दोनों codebases के scale या duration की तुलना सिर्फ Rust और Zig के आधार पर सीधे करना मुश्किल है

नया compiler जो developer experience देता है

  • hot code loading और cross-compilation

    • roc server.roc से server चलाने के बाद code edit करने पर अगले request से नया code अपने आप लागू हो जाता है
    • Python जैसी interpreted languages में यह आम है, लेकिन Roc जैसी high-performance compiled language में यह सामान्य नहीं है
    • यह web server के साथ-साथ simple 2D games में भी इसी तरह काम करता है
    • deploy करते समय roc build server.roc LLVM optimizations लागू करके standalone binary बनाता है
    • roc build --target=x64musl से Alpine Linux के लिए static binary cross-compile की जा सकती है
    • समान source bytes input करने पर Mac समेत किसी भी build system पर समान output bytes generate होते हैं
  • pattern matching के अंदर string interpolation

    • "/users/${id}" जैसे string interpolation को pattern matching के अंदर support करता है
    • Express-style routing की तरह runtime में template string parse करने के बजाय compiler इसे सीधे handle करता है
    • HTTP method और path को साथ में match किया जा सकता है, और nested paths या default values पर भी patterns से branch किया जा सकता है
    • example का पूरा HTTP request handling code compile time पर type safety जांचते हुए बिल्कुल heap allocation नहीं करता
    • pure functions के compile-time execution का उपयोग करके HTTP request routing को allocation-free तरीके से implement किया गया है, और homepage के WebAssembly compiler में syntax test किया जा सकता है

शुरुआत से फिर लिखने की वजह

  • Rust·C·Zig के विपरीत Roc system language नहीं है, और reference counting based automatic memory management का उपयोग करता है
    • tracing garbage collector के pauses से बचता है
    • Perceus optimization और Koka-style opportunistic mutation का फायदा उठाता है
  • सामान्य non-system languages की तरह हर closure capture पर heap allocate नहीं करता, बल्कि lambda set specialization के जरिए polymorphic defunctionalization का उपयोग करता है
  • defunctionalization functional languages में inlining की तरह कई follow-up optimizations संभव बनाता है, लेकिन मौजूदा implementation को सही बनाना बेहद कठिन था
  • Ayaz Hafiz के OCaml prototype के जरिए यह पुष्टि हुई कि समस्या कई compiler stages में फैली structural defect थी, और इसे हल करने के लिए compiler का अधिकांश हिस्सा फिर से लिखना पड़ेगा
  • अन्य contributors भी अलग-अलग कारणों से कई हिस्सों को दोबारा लिखने की योजना बना रहे थे, इसलिए लगभग पूरे code को धीरे-धीरे बदलने वाले Ship of Theseus approach के बजाय full rewrite पर विचार हुआ
  • सफल compiler projects में self-hosting आदि के लिए शुरुआत से rewrite करना आम है, और अपनी भाषा नहीं बल्कि Go में ले जाए गए TypeScript rewrite का उदाहरण भी है
  • Roc compiler self-host नहीं करेगा, यह नीति है, लेकिन इस बार rewrite से मिलने वाले फायदे ज्ञात लागतों से ज्यादा माने गए

फिर Rust नहीं, Zig चुनने के मानदंड

  • team पहले से standard library की कई basic features में Zig का उपयोग कर रही थी, और जिन system languages को वह पर्याप्त रूप से जानती थी वे Rust और Zig ही थीं, इसलिए केवल इन्हीं दो languages पर गंभीरता से विचार किया गया
  • हर project के लिए suitable language अलग होती है; अन्य कामों में Rust का उपयोग जारी रखना और Roc के लिए Zig चुनना एक-दूसरे के विरोधाभासी नहीं हैं
  • build time

    • Rust के cargo builds incremental build तक में समय लेते थे, और codebase बढ़ने के साथ यह प्रमुख असुविधा बनी रही
    • Zig में जाने से builds काफी तेज होने की उम्मीद थी
  • memory और data layout control

    • हर compiler stage में विभिन्न allocators, खासकर arena allocators, का उपयोग किया जाता है और struct-of-arrays (SoA) layout का व्यापक रूप से फायदा उठाया जाता है
    • Rust ecosystem आम तौर पर एक global allocator मानकर चलता है, जबकि Zig ecosystem granular allocator passing को default मानता है और SoA support भी standard में देता है
  • जरूरी ecosystem का दायरा

    • Rust का पूरा ecosystem Zig से बड़ा है, लेकिन Roc की खास जरूरतों से जुड़े packages दोनों ecosystems में ज्यादा नहीं थे
    • LLVM C++ library को wrap करने के बजाय fast bitcode generate करने वाले code जैसी जरूरी niche capabilities Zig side पर ज्यादा थीं
  • memory-unsafe code support

    • मौजूदा Rust compiler की 3 लाख lines में लगभग 1,200 unsafe usages थे
    • तुलना के लिए, Rust compiler खुद 35 लाख lines में लगभग 40 हजार unsafe का उपयोग करता है
    • machine code generate करने वाले compiler में memory-unsafe operations काम का एक महत्वपूर्ण हिस्सा होते हैं
    • Rust का model rare unsafe blocks को isolate करके Miri या Valgrind से check करने का है, लेकिन Roc में unsafe rare नहीं था
    • Zig ने memory-unsafe code को सही ढंग से चलाने के लिए ज्यादा features दिए, और team को सबसे ज्यादा support इसी area में चाहिए था

borrow checker के बिना मिली memory safety

  • Rust और Zig की जांच का दायरा

    • Microsoft के 2019 के डेटा के अनुसार, हर साल security updates में संभाली जाने वाली कमजोरियों में लगभग 70% memory safety issues थीं
    • उस डेटा के 2018 classification को Rust और Zig के नजरिए से बांटें तो यह इस प्रकार है
    • 83.6% out-of-bounds read/write, unsafe cast, uninitialized read, stack overflow, और non-memory-safety issues थे; यह ऐसी category है जिस पर Rust या Zig चुनने का असर नहीं पड़ता
    • 16.4% use-after-free थे; यह category Rust के borrow checker, Zig ReleaseSafe, और Fil-C style checks से पकड़ी जा सकती है
    • Zig का ReleaseSafe freed memory इस्तेमाल होने पर runtime में panic कर देता है
      • Rust के safe subset की तुलना में इसकी जांच कम व्यापक है
      • इसमें runtime cost है और program panic कर सकता है
    • ReleaseFast production में checks छोड़ देता है, लेकिन debug builds और tests में उन्हें बनाए रखता है
    • अगर सभी वास्तविक execution paths को test किया जाए, तो ReleaseSafe जैसी safety मिल सकती है, लेकिन ऐसी test coverage आम तौर पर realistic नहीं होती
  • दूसरे Zig projects और Rust का unsafe

    • TigerBeetle ReleaseSafe इस्तेमाल करता है, और विस्तृत Jepsen verification में 2 safety bugs मिले, लेकिन दोनों memory safety से संबंधित नहीं थे
    • ReleaseFast इस्तेमाल करने वाले Ghostty और Zig compiler में भी Zig code की memory unsafety से पैदा हुआ कोई CVE नहीं है
    • Rust programs में भी dependencies के अंदर मौजूद unsafe के जरिए memory safety gap बन सकता है
    • Unsafe Rust में ReleaseFast Zig जैसा risk है, लेकिन development के दौरान issues पकड़ने वाली Zig की runtime checks जैसी सुविधा इसमें नहीं है
    • Miri और Valgrind मदद कर सकते हैं, लेकिन इन्हें इस्तेमाल करने वाले Rust projects ज्यादा नहीं हैं
    • इसके बजाय unsafe को कम इस्तेमाल करने और अधिक सख्ती से review करने की culture ही practice में Rust की मजबूत memory safety reputation बनाती है
    • Rust-based projects में भी unsafe से जुड़ी vulnerabilities आने के उदाहरण हैं
      • Deno में out-of-bounds read और use-after-free CVE थे
      • Rocket में use-after-free CVE था
      • Actix में उस दौर में कई memory-unsafety CVE आए जब उसका unsafe usage असामान्य रूप से ज्यादा था
    • Roc में allocations का अधिकांश हिस्सा ऐसे arenas में होता है जिनकी lifetime simple होती है, इसलिए use-after-free को बड़ा risk नहीं माना गया, और यह आकलन किया गया कि Zig की extra checks मूल रूप से unsafe code में ज्यादा उपयोगी हैं

rewrite के बाद पुष्टि हुए memory corruption bugs

  • Claude Opus 4.8 से Roc issue tracker को classify करने पर परिणाम इस प्रकार रहे
compiler memory corruption हुआ memory corruption नहीं कुल
Rust 21 2,575 2,596
Zig 10 421 431
  • Rust compiler के 21 memory corruption मामले compiler internal logic की corruption नहीं थे
    • borrow checker ने अपना intended role निभाया
    • ये miscompilation bugs थे, जहां गलत generated machine code ने compiled program में memory corrupt की
  • Zig compiler के 10 memory corruption मामलों में से 8 भी miscompilation थे
  • बाकी 2 cases error-reporting code में use-after-free थे
    • roc check और roc bundle के error messages में filenames U+FFFD replacement character में टूट गए थे
    • अगर Rust borrow checker होता, तो वह दोनों bugs पकड़ सकता था
  • अलग tool choice का real users पर असर इस प्रकार होता
tool choice वास्तविक memory safety impact
Zig ReleaseFast कुछ error messages में filename render न होने वाले 2 bugs
Zig ReleaseSafe कुछ errors में panic होने और filename render न कर पाने वाले 2 bugs
Rust borrow checker दोनों bugs की रोकथाम
  • 18 महीने, लाखों lines of code, और सैकड़ों bug reports को देखते हुए, इन तीन विकल्पों का फर्क project के लिए practically बहुत बड़ा नहीं था
  • Bun में JavaScript के tracing garbage collection values और manually managed values को साथ संभालते समय use-after-free, double-free, और missing free का बड़ा हिस्सा था
  • Roc compiler JavaScript या किसी अन्य tracing garbage collector से integrate नहीं करता, इसलिए उसे वही lifetime management problem नहीं आती
  • Roc को compiler internals की memory errors से ज्यादा generated output code की memory errors खोजने वाले tools की जरूरत है, और बाद वाला borrow checker के scope से बाहर है

build time के वास्तविक परिणाम

  • zig build --watch -fincremental फिलहाल लगभग 450K–460K lines के Zig code में changes को करीब 35ms में फिर से build करता है
  • stable version Zig 0.16.0 में Roc codebase पर -fincremental को तोड़ने वाला bug है
    • fix merge हो चुका है, लेकिन उसे इस्तेमाल करने के लिए compatibility-breaking Zig 0.17.0 pre-build पर जाना होगा
    • संबंधित dependencies को भी साथ में vendor करके 0.17.0 पर upgrade करना होगा, इसलिए अगली stable release का इंतजार करने का फैसला किया गया
  • Intel desktop और Ubuntu 26 पर measured results इस प्रकार हैं
Roc compiler code size cold build incremental build
Rust 1.85.0 based original 354K 32.4 सेकंड 10.0 सेकंड
Rust 1.97.0 based original 354K 25.4 सेकंड 3.4 सेकंड
Zig 0.16.0 feature parity point 320K 39.6 सेकंड 8.6 सेकंड
Zig 0.17.0 current rewrite 464K 32.1 सेकंड 0.035 सेकंड
  • feature parity point पर लगभग न बदलने वाले artifacts भी हर बार फिर से build होते थे, लेकिन अब उन्हें केवल जरूरत पड़ने पर generate किया जाता है
    • इस change से code करीब 50% बढ़ा, फिर भी cold build तेज हो गया
  • Rust 1.85.0 से 1.97.0 पर जाने पर incremental build 10 सेकंड से घटकर 3.4 सेकंड हो गया, यानी 18 महीनों में लगभग दो-तिहाई improvement हुआ
  • Zig का 35ms, 3.4 सेकंड का लगभग 1/100 है, और यह Rust measurement वाले code से करीब 50% बड़े codebase पर हासिल परिणाम है
  • अभी -fincremental केवल x86-64 पर काम करता है और कई contributors ARM-based Mac इस्तेमाल करते हैं, इसलिए build time advantage का पूरा फायदा अभी नहीं मिल पा रहा

Pointer-रहित संरचना और parsing-रहित deserialization

  • नया disk cache, Zig compiler और game development में इस्तेमाल होने वाले तरीके की तरह, execution के लिए efficient memory layout को ही disk format के रूप में इस्तेमाल करता है
  • सभी compiler data structures को pointer के बजाय 32-bit index arrays के रूप में व्यक्त किया जाता है, और कई जगह struct array format का उपयोग होता है
    • Memory usage घटता है और execution speed बढ़ती है
    • Data structure को अलग serialization format में बदले बिना सीधे disk पर लिखा जा सकता है
  • Deserialization के समय disk bytes को parse नहीं किया जाता
    • Bytes को memory में पढ़ा जाता है
    • Existing data structures को कुछ relocate किया जाता है ताकि वे नए arrays की ओर point करें
    • इसके बाद इन्हें तुरंत इस्तेमाल किया जा सकता है
  • Speed मूल रूप से disk से memory में bytes पढ़ने की I/O speed से सीमित होती है
    • अगर data operating system के disk cache में हो, तो पिछले build results लगभग memcpy speed पर load होते हैं
  • roc check पहली run में parsing और type checking जैसे results को disk पर save करता है
    • अगर input source नहीं बदला है, तो दूसरी run में data structures को सीधे disk से memory में ले आता है
  • roc test deterministic pure function test results को भी cache करता है
  • Cache file-level पर काम करता है, इसलिए एक file बदलने पर सिर्फ वही file और dependent files फिर से process होती हैं
  • यह तरीका इसलिए संभव है क्योंकि पूरा compiler pointers के बजाय indexes का इस्तेमाल करता है; सामान्य pointer-centric structures में parsing-रहित deserialization संभव नहीं होता
  • Index-based structure की safety limits

    • जैसे pointer गलत address की ओर point कर सकता है, वैसे ही index भी गलत array lookup करके arbitrary bytes पढ़ सकता है
    • Rust borrow checker pointer lifetimes को संभालता है, लेकिन कौन-सा index किस array से संबंधित है, यह check नहीं करता
    • अगर arrays की ज़रूरी संख्या पहले से पता हो, तो Rust का compact_arena macros से type tags बनाकर गलत array lookup रोक सकता है
    • Roc की तरह modules की संख्या के हिसाब से arrays की संख्या बदलती हो, तो यह technique लागू नहीं की जा सकती, इसलिए compact_arena भी SmallArena::new को unsafe mark करता है
    • खाली arena बनाना अपने-आप में खतरा नहीं बनाता; असली जोखिम बहुत बार होने वाली array indexing में है
    • Safe Rust इस assumption पर प्रभावी होता है कि unsafe छोटा और isolated है, लेकिन Roc की तरह unsafe व्यापक हो तो यह assumption सही नहीं रहता

Roc के लिए अनुकूल Zig ecosystem

  • Bun में JavaScript और manual memory की interoperability के कारण Rust का Drop, जो cleanup code को सिर्फ एक बार चलाता है, उपयोगी था
  • इसके उलट Roc हर module और compilation stage के लिए अलग arena इस्तेमाल करना चाहता था, इसलिए global allocator और implicit Drop को मानकर बने Rust packages असुविधाजनक थे
  • Zig ecosystem में allocator को explicit रूप से pass करने वाले API आम हैं, इसलिए यह Roc के memory management तरीके से अच्छी तरह मेल खाता है
  • Rust ecosystem Bun की चाही हुई structure के लिए, और Zig ecosystem Roc की चाही हुई structure के लिए, क्रमशः ज्यादा उपयुक्त था
  • LLVM bitcode serializer का reuse

    • LLVM, Roc optimizer की मुख्य dependency है और Roc की अपनी optimization के बाद अतिरिक्त optimizations करता है
    • LLVM अक्सर major API compatibility तोड़ देता था, इसलिए version upgrade में काफी समय और लागत लगती थी
    • LLVM का serialized bitcode format stable और backward-compatible है, इसलिए अपना serializer इस्तेमाल करने पर C++ API changes से बचा जा सकता है
    • इसके लिए LLVM C++ library से अलग हाथ से बनाया गया bitcode serializer चाहिए
    • पहले से ज्ञात implementation Zig compiler में था, और Roc का नया compiler उस Zig code को reuse करता है
    • Zig ecosystem से Roc को मिलने वाला सबसे बड़ा dependency source सामान्य packages से ज्यादा Zig compiler खुद है

Zig में याद आने वाली Rust सुविधाएं

  • Compiler implementation में explicit allocation control चाहिए, लेकिन tests में Rust की automatic allocation/deallocation ज्यादा सुविधाजनक थी
    • Zig test allocator memory leaks ढूंढता है और compiled Roc code के leaks भी detect कर सकता है
    • लेकिन हर test में init और defer deinit सही-सही लिखना पड़ता है; एक भी गलती हो तो leak की वजह से test fail हो जाता है
  • Zig का comptime parametric और ad-hoc polymorphism से overlap करता है, लेकिन polymorphism के दोनों रूपों की कमी महसूस होती है
    • Rust का Allocator trait self ले सकता है
    • Zig के ArenaAllocator जैसे implementations को anyopaque pointer लेने के बाद अपने type में cast करना पड़ता है
  • Private struct fields नहीं हैं, इसलिए जिन fields को directly access नहीं करना चाहिए, उन्हें compile error से रोका नहीं जा सकता
    • Code diff review में सिर्फ field access दिखता है, original struct की documentation नहीं दिखती, इसलिए हर बार अलग से check करना पड़ता है
  • Functions, variables और constants सभी के लिए snake_case इस्तेमाल करने वाली Rust की consistency कभी-कभी याद आती है
  • unsafe और borrow checker की लागत भी थी, लेकिन वे यह stability देते थे कि कुछ खास समस्याओं की चिंता सिर्फ unsafe block के अंदर करनी है
    • इसका मतलब यह नहीं कि Zig में वही feature जोड़ा जाना चाहिए
  • Zig में Rust की तुलना में dead code देर से मिलने के मामले ज्यादा हुए
    • Zig built-in tools और TigerBeetle का tidy.zig भी कुछ dead code नहीं पकड़ पाते
    • Dead code binary में generate नहीं होता, इसलिए users पर असर नहीं डालता, लेकिन codebase management के लिए नुकसानदेह है
  • Rust में minor version upgrades और edition changes अधिकांशतः आसान थे
  • मौजूदा Zig backward compatibility को लक्ष्य नहीं बनाता, और यह expected condition थी इसलिए बड़ी समस्या नहीं थी, लेकिन Rust का सरल upgrade experience बेहतर था

Zig में संतोषजनक बातें

  • Zig में functional programming जैसे familiar tools कम करने के बदले अन्य properties हासिल करने वाली subtractive design की appeal है
  • Macros नहीं हैं, और parametric polymorphism सहित कई समस्याओं को comptime या normal functions से हल किया जा सकता है
  • Data layout को बहुत fine-grained तरीके से control किया जा सकता है
    • u7, u5 जैसे 2 की powers न होने वाले integer types को अलग bit processing के बिना इस्तेमाल किया जाता है
    • packed struct को built-in support है
    • Function को declaration site नहीं, बल्कि call site पर inline किया जा सकता है
    • Rust में जिन features के लिए macro-based crates चाहिए, उन्हें extra dependency के बिना इस्तेमाल किया जाता है
  • Zig build toolchain Alpine Linux और WebAssembly के लिए standalone binaries बनाने में अच्छी तरह फिट बैठा
    • Roc standard library के equivalent builtins को opaque binary blob के रूप में compile करके final executable में शामिल करने जैसे special builds भी संभालता है
    • Uber भी Zig language इस्तेमाल किए बिना build infrastructure में Zig toolchain इस्तेमाल करता है
  • Zig की error handling में failed heap allocation को भी normal user-space error के रूप में handle किया जाता है
  • Roc भी anonymous sum types और payloads का उपयोग करके errors को natural तरीके से accumulate करने वाली समान strategy इस्तेमाल करता है
  • Rust के anyhow, thiserror और default Result-based handling की तुलना में Zig और Roc का तरीका अधिक पसंद है
  • Error propagation syntax में Zig के try की बजाय Rust का postfix ? पसंद है, इसलिए Roc ने भी postfix ? operator अपनाया
  • Granular allocator API और high-performance compiler के लिए reusable code सहित, पूरे project के स्तर पर Zig चुनने से काफी संतुष्टि है

Roc के अगले कदम

  • नए compiler की 0.1.0 release इस साल के बाद के हिस्से में planned है, और यह Roc की पहली numbered release होगी
  • Release से पहले भी Nightly builds try किए जा सकते हैं, लेकिन अभी कई bugs, अधूरे features और incomplete documentation बाकी हैं
  • Roc Programming Language Foundation अमेरिका की 501(c)(3) non-profit organization है, और donations मुख्य रूप से contributors को compensate करने में इस्तेमाल होते हैं
  • आगे की development progress और questions Roc Zulip पर देखे जा सकते हैं

2 टिप्पणियां

 
GN⁺ 3 시간 전
Lobste.rs की राय
  • आंकड़ों के हिसाब से Zig का 35ms Rust के 3.4 सेकंड से लगभग 100 गुना तेज़ है और कोड भी करीब 50% ज़्यादा है, लेकिन वास्तविक development में यह अंतर कुछ बढ़ा-चढ़ाकर दिखता है
    अगर compiler को हर मिनट नहीं बल्कि हर 10 मिनट में एक बार rebuild किया जाए, तो बचने वाला समय लगभग 3 सेकंड है, और 3.4 सेकंड भी काफ़ी तेज़ है
    codebase बड़ा होने पर Rust धीमा पड़ सकता है, लेकिन cold build time तो उल्टा और खराब हुआ है, और अगर हर 2 साल में laptop बदला जाए और compiler के अपने सुधार भी जुड़ें, तो यह पक्का कहना मुश्किल है कि Rust का build time लंबी अवधि में लगातार बढ़ता ही जाएगा

    • save करते ही अगली key दबाने या editing position बदलने से पहले compile errors और invalidated test results दिख जाना बहुत बड़ा फ़ायदा है
      Rust का check mode और LSP पूरे recompilation से तेज़ हैं, लेकिन उसी स्तर के नहीं
    • अगर यह मान लिया जाए कि recompilation सिर्फ़ हर 10 मिनट में एक बार होती है, तो अंतर को कम करके आंका जाएगा
      --watch -fincremental combination इस्तेमाल करने पर हर save पर recompilation होती है, जिससे बहुत तेज़ और बार-बार feedback मिलता है
      मैंने अपना personal project Rust से Zig में शिफ्ट किया, और Rust को कई साल काम में भी इस्तेमाल किया है, लेकिन Zig की compile speed सच में राहत देने वाला बदलाव थी
  • 35ms वाकई हैरान करने वाली speed है
    सिर्फ relink करने में भी शायद इससे ज़्यादा समय लगे, इसलिए यह जानने की उत्सुकता है कि compiler वास्तव में क्या कर रहा है

    • Zig में अपना linker है
    • मुख्य बात incremental linking और मौजूदा binary को उसी जगह modify करने की है, और याद पड़ता है कि Roc compiler में इसे surgical linking कहा गया था
      अगर function implementation बदली जाए, तो नया assembly मौजूदा binary में insert किया जा सकता है, लेकिन inlining, function signature change, dependency, या space कम पड़ने पर relocation जैसी चीज़ों के लिए अतिरिक्त analysis चाहिए
    • Zig team ने incremental compilation पर काफ़ी काम किया है, और नया ELF linker अभी अधूरा है, लेकिन इसे individual function और global variable स्तर पर incremental linking को लक्ष्य बनाकर डिज़ाइन किया गया है
      function को modify या add करने पर output executable की .text section में काफ़ी बड़ा खाली क्षेत्र ढूँढकर नया machine code लिखा जाता है
      अगर जगह कम पड़े, तो .text को expand करना और दूसरे data को move करना पड़ सकता है, लेकिन section को exponential तरीके से expand करके cost को फैलाया जाता है
      ज़रूरत पड़ने पर symbol table और relocation entries भी जोड़ी जाती हैं, और इनके लिए भी पहले से खाली जगह छोड़ी जाती है, इसलिए काम के बाद file बंद करना ही काफ़ी होता है
      हाल में देखे गए Tracy results के आधार पर अनुमान है कि कुल 35ms में linker का समय लगभग 1ms ही था
  • 3 लाख lines के Rust code में unsafe का लगभग 1,200 बार इस्तेमाल होना उम्मीद से ज़्यादा लगा
    उदाहरण के लिए Inko में unsafe { ... } expressions सिर्फ़ 162 हैं, और C ABI के ज़रिए generated code में expose होने वाले unsafe functions 87 ही हैं, जबकि कुल Rust code लगभग 88 हज़ार lines है
    हालाँकि Inkwell के काम करने के तरीके की वजह से पूरा LLVM backend व्यवहार में एक बहुत बड़ा unsafe क्षेत्र बन जाता है, इसलिए सिर्फ़ इस तुलना से निष्कर्ष निकालना मुश्किल है
    मैं सिर्फ़ उन functions को unsafe mark करता हूँ जो pointers को सीधे संभालने जैसी चीज़ों से वास्तव में memory safety को नुकसान पहुँचा सकते हैं, लेकिन दूसरे developers कभी-कभी इसे इस संकेत के रूप में भी mark करते हैं कि panic से बचने के लिए पहले कोई खास काम करना होगा

  • Rust standard library और compiler में unsafe 40 हज़ार बार आता है, यह आँकड़ा सही नहीं है
    इसमें tests और comments में आए शब्द तक गिने गए हैं, और इनमें से ज़्यादातर standard library में हैं; compiler खुद में तो comments और tests समेत भी 2 हज़ार से कम हैं
    rustc में योगदान देते समय मेरे PRs में unsafe शामिल होने का अनुपात 1% से भी कम था, यानी compiler के अंदर unsafe code काफ़ी दुर्लभ है
    पूरे Rust जिस foundational runtime पर निर्भर करता है, उसे implement करने वाली standard library में unsafe ज़्यादा होना स्वाभाविक है, और ऐसा code किसी भी language में, चाहे explicitly हो या implicitly, unsafe ही होता है
    वह compiler rustc से लगभग 10 गुना छोटा है, लेकिन unsafe का इस्तेमाल लगभग उतना ही है, इसलिए इसे सर्वव्यापी कहना ठीक नहीं होगा, पर यह काफ़ी बार दिखता है, और यह जानने की जिज्ञासा है कि rustc से कहीं ज़्यादा इसकी ज़रूरत क्यों पड़ी

    • हमारे codebase में भी compiler और standard library मिले हुए हैं, और standard library का कुछ हिस्सा compiler builtins के रूप में implement किया गया है, इसलिए उस metric में तुलना काफ़ी हद तक समान चीज़ों की थी
      हमने दोनों codebases में standard library से जुड़े unsafe का अलग से analysis नहीं किया, लेकिन अगर नया compiler Rust में implement किया गया होता, तो caching और pointers की जगह indices इस्तेमाल करने वाली संरचना के कारण संभवतः rustc से ज़्यादा unsafe होता
      Rust team के काम को कमतर दिखाने का इरादा नहीं था; हम सिर्फ़ दूसरे projects की उपलब्धियों का सम्मान करते हुए अपनी पसंद और प्रगति को सामने रखना चाहते थे
  • यह लेख Rust और Zig, दोनों की खूबियों और कमियों का संतुलित आकलन करता है बिना कुछ छिपाए; खासकर यह निष्कर्ष बहुत ईमानदार लगा कि 18 महीनों तक लाखों लाइनों का कोड लिखने और सैकड़ों bugs संभालने के बाद भी, अगर उपलब्ध विकल्पों में से कोई दूसरी भाषा चुनी जाती, तो project का नतीजा शायद इतना अलग नहीं होता
    हालांकि Roc या rustc जैसे machine code जनरेट करने वाले compiler में memory-unsafe operations काम का बड़ा हिस्सा होते हैं, यह बात समझना कठिन है
    OCaml या Haskell में लिखे गए compiler भी बहुत हैं, और machine code generation अपने-आप में बस bytes को एक vector में बनाकर file में लिखने का काम है, इसलिए इसके unsafe होने की कोई वजह नहीं दिखती
    interpreting या JIT compilation हो तो बात समझ में आती है, लेकिन सामान्य compilation में भी इसकी जरूरत क्यों पड़ती है, यह जानने की जिज्ञासा है

    • machine code जनरेट करना अपने-आप में सिर्फ bytes लिखने का काम है, इसलिए उसे unsafe कहना सटीक नहीं था
      जोखिम तब पैदा होता है जब जनरेट किया गया machine code चलाया जाता है, और वास्तव में compiler अक्सर machine code को जनरेट भी करते हैं और साथ ही चलाते भी हैं, इसलिए इसे काम का बड़ा हिस्सा कहा गया
      interpreting और JIT के अलावा, Rust के const fn या Roc में top-level तक hoist किए जा सकने वाले expressions की तरह compile time पर user code evaluate करना, या tests चलाने के बाद output जांचकर यह तय करना कि user को क्या दिखाना है, जैसे काम भी इसमें शामिल हैं
    • इसका मतलब शायद यह है कि Zig compiler जैसे बहुत तेज़ incremental compiler बनाने के लिए low-level language और काफ़ी unsafe operations की जरूरत पड़ती है
    • लेख में गलत compilation की वजह से memory safety bug पैदा होने की स्थिति को भी compiler की memory safety bug में गिना गया है
      चाहे corrupt हुई memory compiler process में हो या generated program में, processor के गलत व्यवहार की वजह compiler द्वारा बनाए गए instructions ही हैं, और जिसे ठीक करना है वह भी compiler code ही है — यही इसका मानदंड है
    • Roc ने compiler को Rust में लिखे जाने के दौर में भी runtime और standard library के लिए Zig का इस्तेमाल किया था
      external linked code और garbage collector जैसे components को Rust में भी बहुत unsafe चाहिए होता है, जिससे safe Rust के फायदे काफी हद तक कम हो जाते हैं
      compiler को अकेले safe language में लिखा जा सकता है, लेकिन performance समस्या बनती है; Zig और Roc compiler, दोनों array-of-structs के बजाय struct-of-arrays (SoA) और pointers की जगह array indices का व्यापक उपयोग करते हैं
      इसे Rust में लागू करने पर borrow checker को bypass करना पड़ता है, जिससे उससे जुड़े safety benefits खो जाते हैं
      compiler की lifetime relationships अपेक्षा से अधिक सरल हैं: एक phase में arena में data allocate, generate और modify किया जाता है, फिर अगले phase को read-only रूप में दे दिया जाता है, और उसके बाद पूरी arena को फेंका जा सकता है
      अधिक जटिल हिस्सा incremental compilation और linking है, जहाँ disk से state पढ़ी जाती है और binary को in-place modify किया जाता है; इस प्रक्रिया में state corruption, bugs, गलत compilation और memory problems हो सकती हैं, लेकिन यह compiler process की memory safety से अलग बात है
      safety और correctness की अवधारणा, किसी program के अंदर की memory safety से कहीं अधिक व्यापक है, खासकर तब जब Rust में कठिनाई से व्यक्त होने वाले कामों को सुरक्षित और सही ढंग से करना हो
  • compact_arena के creator के रूप में उल्लेख किया जाना अच्छा लगा, लेकिन इस library में unsafe के उपयोग को गलत तरीके से पेश किया गया है
    compact_arena का उद्देश्य यह सुनिश्चित करना है कि safe Rust में new का गलत उपयोग न हो सके, ताकि indexing को safe बनाया जा सके
    mk_arena macro को उन loops के अंदर भी safe Rust code में call किया जा सकता है जहाँ जरूरत पड़ने वाली arenas की संख्या पहले से ज्ञात न हो

    • हो सकता है मैं new के safe उपयोग के बारे में दी गई व्याख्या को ठीक से न समझ पाया हूँ, लेकिन https://docs.rs/compact_arena/0.5.0/… के safety section में लिखा है कि constructor को दिया जाने वाला tag ही indexing technique की नींव है, और अगर इस value को किसी दूसरी arena में इस्तेमाल किया जाए तो दोनों arenas के indices मिल सकते हैं, जिससे out-of-bounds access और undefined behavior हो सकता है
      मेरा कहना यह था कि जोखिम new() call में नहीं, बल्कि new() से बने value पर indexing करते समय पैदा होता है, और audit करके unsafe के रूप में चिह्नित किया जाना चाहिए तो वह असल indexing call site होनी चाहिए
      हालांकि यह भी संभव है कि मैंने design को गलत समझा हो
  • मेरी जानकारी में Python में नया code लागू करने के लिए program को restart करना पड़ता है, और hot reloading के लिए non-standard extensions तथा बहुत सावधानी से लिखा गया code चाहिए होता है
    hot reloading, Lisp या Smalltalk जैसे image-based development environments या Erlang में अधिक सामान्य है

    • importlib.reload() का उपयोग करने पर Python में भी hot reloading संभव है, लेकिन यह कितनी व्यापक रूप से इस्तेमाल होता है, यह मुझे नहीं पता
  • यह जानने की जिज्ञासा है कि cargo build का उपयोग न करने पर क्या build time घटाया जा सकता है
    Bazel को configure करने का काम Zig में port करने से बड़ा है या छोटा, यह स्पष्ट नहीं है; और निश्चित ही build time ही भाषा बदलने की एकमात्र वजह नहीं थी

 
GN⁺ 4 시간 전
Hacker News की रायें
  • लेख कुल मिलाकर ठीक है, लेकिन Roc या rustc जैसे मशीन कोड जनरेट करने वाले compiler में memory-unsafe operations काम का बड़ा हिस्सा होते हैं—इस बात से सहमत होना मुश्किल है
    runtime में binary patching या code reloading के लिए unsafe code की जरूरत हो सकती है, लेकिन सामान्य रूप से executable बनाने की प्रक्रिया में मशीन कोड generation अपने-आप unsafe होने की कोई वजह नहीं है
    बल्कि भाषा के runtime में unsafe code मिलने की संभावना ज्यादा है

    • सहमत हूं। मशीन कोड generation तो बस bytes लिखने का काम है, और संभावित unsafety तब पैदा होती है जब उस मशीन कोड को execute किया जाता है
      हालांकि व्यवहार में कई compiler मशीन कोड जनरेट करते समय उसे सीधे execute भी करते हैं, इसलिए “काम का बड़ा हिस्सा” कहा गया था; लेकिन compiler को ये दोनों काम अनिवार्य रूप से करने ही हों, ऐसा नहीं है
      मेरे मन में binary patching, code reloading, runtime के अलावा Rust के const fn या Roc में top-level पर hoist किए जा सकने वाले expressions जैसे compile time पर user code evaluate करना, tests चलाना और output inspect करके दिखने वाली सामग्री तय करना वगैरह भी था
    • memory-safe languages में भी unsafe code blocks होते हैं—इस तथ्य को तोड़-मरोड़कर अक्सर लोग “तो फिर memory safety का क्या फायदा” जैसा निष्कर्ष निकाल लेते हैं
      यह logic कुछ ऐसा है कि seatbelt लगाने पर भी सिर टकरा सकता है, इसलिए seatbelt बस झंझट है और उसे इस्तेमाल नहीं करना चाहिए
    • मशीन कोड generation के लिए unsafe operations मूल रूप से जरूरी नहीं हैं, लेकिन मुझे लगता है कि इसमें अलग-अलग priorities झलकती हैं
      extreme high-performance code में data structures और algorithms से लेकर memory allocation strategy तक बदल जाती है, और TigerBeetle इस बात के लिए प्रसिद्ध है कि वह start होते समय सारी memory एक साथ allocate कर लेता है
      Roc compiler भी Zig जैसा trade-off चुनना चाहता है, इसलिए कई common patterns दिखना स्वाभाविक है
    • मशीन कोड generation के लिए unsafe operations चाहिए—यह बात निश्चित रूप से गलत है। compiler पूरी तरह abstracted data structures के साथ भी काम कर सकता है, और जहां सचमुच memory-unsafe operations की जरूरत पड़ सकती है, वह जगह linker के ज्यादा करीब है
    • यह समझना मुश्किल है कि वास्तव में मशीन कोड बनाने के लिए compiler के किस हिस्से में memory-unsafe operations जरूरी होते हैं
  • ReleaseSafe runtime checks के जरिए use-after-free पकड़ता है—इस दावे का कोई आधार नहीं दिखता
    Zig docs में runtime memory safety checks देखे, लेकिन use-after-free, UaF, safety-checked से search करने पर भी संबंधित जानकारी नहीं मिली; release build में DebugAllocator इस्तेमाल करने पर भी यह इसे भरोसेमंद तरीके से detect नहीं करता
    संबंधित लेख https://landaire.net/memory-safety-by-default-is-non-negotia... में संक्षेप में लिखा है

    • Zig code काफी लिखने के अनुभव से कहूं तो, अगर ऐसी functionality है भी, तो मेरे code में उसने कभी ठीक से काम नहीं किया
      यह judgement AI से पहले खुद code लिखते हुए बनी थी, और Zig व LLM के combination में शायद कुछ अलग हो सकता है; लेकिन लेख पढ़ते-पढ़ते अजीब तरह से रखे गए और दावे दिखते गए, इसलिए भरोसा करना मुश्किल होता गया
      यह किसी ईमानदार technical judgement से ज्यादा पुराने debate को justify करने के लिए लिखा लेख लगता है; फिर भी मुझे अजीब लेख और languages पसंद हैं और अत्यधिक AI hype से भी झुंझलाहट है, इसलिए फिलहाल इसे good faith में देखने की कोशिश कर रहा हूं
    • मेरी समझ में ReleaseSafe बस bounds checks जोड़ता है और unreachable code पर panic कराता है
      Zig temporal memory safety प्रदान करता हुआ नहीं दिखता
  • यह दिलचस्प है कि mature OCaml flexible और expressive enough होने के कारण prototype validation में इस्तेमाल हुआ, लेकिन final implementation language के रूप में नहीं चुना गया
    यह भी सवाल है कि Zig का incremental build dune से meaningful तरीके से तेज होगा या नहीं; और cross-compilation इसका advantage है, फिर भी “क्यों Zig?” में इसे नहीं छुआ गया
    compiler में fine-grained memory control सचमुच कितना महत्वपूर्ण है, और OCaml से शुरू हुए Rust व WASM की तरह किस milestone पर maintainers दूसरी language में switch करने का निर्णय लेते हैं—यह भी जानना चाहूंगा

    • Rust ने खुद को Rust में rewrite करने का फैसला करते हुए OCaml से दूरी बनाई थी। लेख में भी इसे पूरे project को rewrite करने के आम समय के रूप में इशारा किया गया है, और मैं उस judgement से सहमत हूं
    • Roc के core goals में से एक compiler speed है। मेरा अनुमान है कि OCaml system programming language नहीं है, इसलिए उसे candidate list से बाहर रखा गया
  • Zig का incremental build निश्चित रूप से निर्णायक feature है, और short term में इसे पाने के लिए language बदलने का फैसला समझ में आता है
    लेकिन medium term में उम्मीद है कि Rust में भी निकट भविष्य में ऐसी ही functionality जुड़ जाएगी
    speed चाहिए, लेकिन तेजी में अपने ही पैर पर कुल्हाड़ी नहीं मारना चाहता; मैं Rust की safety, Zig की features, और garbage collection के बिना Go runtime को मिलाकर एक language खुद बना रहा हूं

    • तेज Rust builds पर पहले से https://rust-lang.github.io/rust-project-goals/2026/roadmap-... में काम चल रहा है, और इस page के अधिकांश goals इसी साल के लिए हैं
    • Zig के ज्यादा safe होने से काफी पहले Rust compile times तेज होने की संभावना ज्यादा है
    • Rust compiler के तेज होने का इंतजार करने के बजाय Zig में किसी तरह का borrow checker जोड़ने का रास्ता कैसा रहेगा, यह सोचता हूं
      यह ज्यादा feasible लगता है, और user space में implement करना भी संभव दिखता है
    • garbage collection हटाने के अलावा Go runtime का खास advantage क्या है, यह जानना चाहूंगा
    • Rust की safety और Zig की features को combine करने की कोशिश में बचे हुए Codex tokens कभी-कभी https://github.com/ityonemo/clr पर इस्तेमाल कर रहा हूं
  • compiler की implementation language चुनने की प्रक्रिया में अगर सच में scientific comparison किया गया होता, तो बात कहीं ज़्यादा convincing होती
    ऐसा लगता है कि शुरुआत इस अप्रमाणित assumption (https://www.roc-lang.org/faq#self-hosted-compiler) से हुई कि high-performance compiler के लिए low-level systems language चाहिए, और फिर Rust के अलावा Zig ही इकलौता विकल्प है—ऐसा निष्कर्ष निकाल लिया गया
    compiler performance पर algorithms हावी होते हैं, और तेज़ managed languages भी उसी algorithm के साथ आम तौर पर execution time में दो गुना के भीतर रहती हैं, जबकि algorithms के अंतर से होने वाले performance gap की कोई तय सीमा नहीं होती
    Zig खुद इस theory का counterexample है कि low-level language में compiler लिखने से वह तेज़ हो जाता है, और Roc का लगभग 15 हज़ार lines per second तेज़ नहीं है। 1998 में भी ML compiler के 3 हज़ार lines per second process करने का reference मौजूद है (https://flint.cs.yale.edu/cs421/case-for-ml.html)
    अभी compiler का काम रोककर, 10 हज़ार lines से कम में बन सकने वाले Roc के एक छोटे subset का self-hosting compiler बनाना भविष्य के लिए ज़्यादा मददगार हो सकता है
    इससे 3 लाख lines वाली implementation के बजाय 10 हज़ार lines के scale पर कई implementations आज़माई जा सकेंगी, और यह verify किया जा सकेगा कि performance targets के लिए low-level language सच में ज़रूरी है या नहीं
    self-hosting की प्रक्रिया में Roc के सच में important features सामने आएंगे, Roc code भी ज़्यादा लिखा जाएगा, और compiler के लिए ज़रूरी general-purpose features सुधारने से downstream applications भी साथ में बेहतर होंगी

    • science की मांग करने के हिसाब से यह assessment भी काफी categorical है और इसमें empirical evidence ज़्यादा नहीं है
      1990s में ML को तेज़ी से compile किया गया था—सिर्फ इस fact के आधार पर आज Roc की compile speed को judge करना मुश्किल है। वजह यह है कि language design ज़रूरी algorithms पर कड़े constraints डालता है, और modern hardware भी कहीं ज़्यादा complex है
      Roc में कुछ हद तक overloading है और heap पर closures allocate न करने के लिए sophisticated algorithms भी लगते हैं; ऐसी requirements ऐसी algorithmic complexity पैदा कर सकती हैं जिसे हटाया नहीं जा सकता
      जब algorithmic optimization की limit तक पहुंच जाते हैं, तो बचता है constant factors घटाना, और खासकर memory manage करने वाली high-level languages इस बात की साफ़ lower bound बनाती हैं कि इन्हें कितना कम किया जा सकता है
      real code में memory layout को सीधे control करके performance में 10 गुना से ज़्यादा improvement होते देखा है, और game industry में ऐसे काम career का बड़ा हिस्सा भी होते हैं। किसी एक clever algorithm से सभी performance problems गायब हो जाएं—ऐसा environment reality से काफी दूर है
  • Rust builds हर computer पर storage space की बड़ी बर्बादी के कारणों में से एक हैं, और कई libraries build करने पर दर्जनों GB बहुत जल्दी जमा हो जाते हैं
    dependencies को projects के बीच reuse करने के लिए global build folder set किया जा सकता है, लेकिन कोई भी solution हो, उसे default behavior के तौर पर देना बेहतर होगा

    • यह faster builds के लिए disk space से trade-off करने वाला structure है, और कुछ cases में ज़्यादा space इस्तेमाल करके speed और बढ़ाई जा सकती है
      हालांकि cache garbage collection ज़रूरी है, और इसे आसान बनाने के लिए intermediate build artifacts के नए layout पर काम final stage में है
    • JavaScript ecosystem में node_modules के size की लगातार criticism को सोचें, तो यह interesting contrast है
      Tauri project में, जहां frontend की तुलना में backend code काफी छोटा है, फिर भी Rust build artifacts 9GB हैं, और node_modules सिर्फ 550MB है
  • मैं Roc को C ABI में embed होने वाली scripting language की तरह समझ रहा हूं, लेकिन इसके असल use cases जानना चाहता हूं
    क्या यह बड़े Roc platform देने वाले plugin environment में WASM से compete करना चाहता है, और plugin developer किसी भी language का इस्तेमाल कर सकता है—ऐसे WASM के बजाय application developer Roc layer expose क्यों करे, यह जानना चाहता हूं
    अगर यह छोटे Roc platform इस्तेमाल करने वाली application-level language है, तो क्या server-side HTTP code में Gleam और client code में Elm से compete करना चाहती है—यह भी जानना चाहता हूं

    • खासकर functional languages जैसी पहली बार दिखने वाली languages किन applications में इस्तेमाल होती हैं, यह जानना मुझे पसंद है, लेकिन roc-lang.org और FAQ पढ़ने के बाद भी Roc के concrete use cases समझ नहीं आए
  • compile time बहुत ज़्यादा underestimated factor है। C++ build के लिए 10-10 मिनट इंतज़ार करना मेरी सबसे बड़ी शिकायत है और यह developer flow को पूरी तरह तोड़ देता है

    • VS Code में syntax analysis और auto-complete के लिए Rust-Analyzer इस्तेमाल करता हूं, लेकिन हर बार file save करने पर information update करने के लिए compiler चलाता है, इसलिए बहुत slow है
      .rs file से .ts file पर बदलने पर ऐसा difference महसूस होता है जैसे computer ही बदल दिया हो
  • Zig को सीधे इस्तेमाल नहीं करता, लेकिन कुछ possibilities को लेकर काफी उम्मीद है
    C से ज़्यादा convenient Zig में लिखे नए games, TigerBeetle में पहले ही potential दिखा चुका distributed software, और personally मेरी रुचि वाले robotics field को लेकर खास उम्मीद है