Roc कंपाइलर को Rust से Zig में फिर से लिखने की प्रगति
(rtfeldman.com)- मौजूदा 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.rocLLVM 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 के
cargobuilds incremental build तक में समय लेते थे, और codebase बढ़ने के साथ यह प्रमुख असुविधा बनी रही - Zig में जाने से builds काफी तेज होने की उम्मीद थी
- Rust के
-
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
unsafeusages थे - तुलना के लिए, Rust compiler खुद 35 लाख lines में लगभग 40 हजार
unsafeका उपयोग करता है - machine code generate करने वाले compiler में memory-unsafe operations काम का एक महत्वपूर्ण हिस्सा होते हैं
- Rust का model rare
unsafeblocks को isolate करके Miri या Valgrind से check करने का है, लेकिन Roc मेंunsaferare नहीं था - Zig ने memory-unsafe code को सही ढंग से चलाने के लिए ज्यादा features दिए, और team को सबसे ज्यादा support इसी area में चाहिए था
- मौजूदा Rust compiler की 3 लाख lines में लगभग 1,200
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 का
ReleaseSafefreed memory इस्तेमाल होने पर runtime में panic कर देता है- Rust के safe subset की तुलना में इसकी जांच कम व्यापक है
- इसमें runtime cost है और program panic कर सकता है
ReleaseFastproduction में 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 में
ReleaseFastZig जैसा 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 आए जब उसका
unsafeusage असामान्य रूप से ज्यादा था
- Roc में allocations का अधिकांश हिस्सा ऐसे arenas में होता है जिनकी lifetime simple होती है, इसलिए use-after-free को बड़ा risk नहीं माना गया, और यह आकलन किया गया कि Zig की extra checks मूल रूप से unsafe code में ज्यादा उपयोगी हैं
- TigerBeetle
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 लगभग
memcpyspeed पर load होते हैं
- अगर data operating system के disk cache में हो, तो पिछले build results लगभग
roc checkपहली run में parsing और type checking जैसे results को disk पर save करता है- अगर input source नहीं बदला है, तो दूसरी run में data structures को सीधे disk से memory में ले आता है
roc testdeterministic 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_arenamacros से type tags बनाकर गलत array lookup रोक सकता है - Roc की तरह modules की संख्या के हिसाब से arrays की संख्या बदलती हो, तो यह technique लागू नहीं की जा सकती, इसलिए
compact_arenaभीSmallArena::newकोunsafemark करता है - खाली 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 का
comptimeparametric और ad-hoc polymorphism से overlap करता है, लेकिन polymorphism के दोनों रूपों की कमी महसूस होती है- Rust का
Allocatortraitselfले सकता है - Zig के
ArenaAllocatorजैसे implementations कोanyopaquepointer लेने के बाद अपने type में cast करना पड़ता है
- Rust का
- 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 देते थे कि कुछ खास समस्याओं की चिंता सिर्फunsafeblock के अंदर करनी है- इसका मतलब यह नहीं कि Zig में वही feature जोड़ा जाना चाहिए
- Zig में Rust की तुलना में dead code देर से मिलने के मामले ज्यादा हुए
- Zig built-in tools और TigerBeetle का
tidy.zigभी कुछ dead code नहीं पकड़ पाते - Dead code binary में generate नहीं होता, इसलिए users पर असर नहीं डालता, लेकिन codebase management के लिए नुकसानदेह है
- Zig built-in tools और TigerBeetle का
- 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और defaultResult-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 टिप्पणियां
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 लंबी अवधि में लगातार बढ़ता ही जाएगा
Rust का
checkmode और LSP पूरे recompilation से तेज़ हैं, लेकिन उसी स्तर के नहीं--watch -fincrementalcombination इस्तेमाल करने पर हर save पर recompilation होती है, जिससे बहुत तेज़ और बार-बार feedback मिलता हैमैंने अपना personal project Rust से Zig में शिफ्ट किया, और Rust को कई साल काम में भी इस्तेमाल किया है, लेकिन Zig की compile speed सच में राहत देने वाला बदलाव थी
35ms वाकई हैरान करने वाली speed है
सिर्फ relink करने में भी शायद इससे ज़्यादा समय लगे, इसलिए यह जानने की उत्सुकता है कि compiler वास्तव में क्या कर रहा है
अगर function implementation बदली जाए, तो नया assembly मौजूदा binary में insert किया जा सकता है, लेकिन inlining, function signature change, dependency, या space कम पड़ने पर relocation जैसी चीज़ों के लिए अतिरिक्त analysis चाहिए
function को modify या add करने पर output executable की
.textsection में काफ़ी बड़ा खाली क्षेत्र ढूँढकर नया 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 होने वालेunsafefunctions 87 ही हैं, जबकि कुल Rust code लगभग 88 हज़ार lines हैहालाँकि Inkwell के काम करने के तरीके की वजह से पूरा LLVM backend व्यवहार में एक बहुत बड़ा
unsafeक्षेत्र बन जाता है, इसलिए सिर्फ़ इस तुलना से निष्कर्ष निकालना मुश्किल हैमैं सिर्फ़ उन functions को
unsafemark करता हूँ जो pointers को सीधे संभालने जैसी चीज़ों से वास्तव में memory safety को नुकसान पहुँचा सकते हैं, लेकिन दूसरे developers कभी-कभी इसे इस संकेत के रूप में भी mark करते हैं कि panic से बचने के लिए पहले कोई खास काम करना होगाRust standard library और compiler में
unsafe40 हज़ार बार आता है, यह आँकड़ा सही नहीं हैइसमें 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 से कहीं ज़्यादा इसकी ज़रूरत क्यों पड़ीहमने दोनों 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 चलाया जाता है, और वास्तव में compiler अक्सर machine code को जनरेट भी करते हैं और साथ ही चलाते भी हैं, इसलिए इसे काम का बड़ा हिस्सा कहा गया
interpreting और JIT के अलावा, Rust के
const fnया Roc में top-level तक hoist किए जा सकने वाले expressions की तरह compile time पर user code evaluate करना, या tests चलाने के बाद output जांचकर यह तय करना कि user को क्या दिखाना है, जैसे काम भी इसमें शामिल हैंचाहे corrupt हुई memory compiler process में हो या generated program में, processor के गलत व्यवहार की वजह compiler द्वारा बनाए गए instructions ही हैं, और जिसे ठीक करना है वह भी compiler code ही है — यही इसका मानदंड है
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_arenamacro को उन 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 ही भाषा बदलने की एकमात्र वजह नहीं थी
Hacker News की रायें
लेख कुल मिलाकर ठीक है, लेकिन Roc या rustc जैसे मशीन कोड जनरेट करने वाले compiler में memory-unsafe operations काम का बड़ा हिस्सा होते हैं—इस बात से सहमत होना मुश्किल है
runtime में binary patching या code reloading के लिए unsafe code की जरूरत हो सकती है, लेकिन सामान्य रूप से executable बनाने की प्रक्रिया में मशीन कोड generation अपने-आप unsafe होने की कोई वजह नहीं है
बल्कि भाषा के runtime में unsafe code मिलने की संभावना ज्यादा है
हालांकि व्यवहार में कई compiler मशीन कोड जनरेट करते समय उसे सीधे execute भी करते हैं, इसलिए “काम का बड़ा हिस्सा” कहा गया था; लेकिन compiler को ये दोनों काम अनिवार्य रूप से करने ही हों, ऐसा नहीं है
मेरे मन में binary patching, code reloading, runtime के अलावा Rust के
const fnया Roc में top-level पर hoist किए जा सकने वाले expressions जैसे compile time पर user code evaluate करना, tests चलाना और output inspect करके दिखने वाली सामग्री तय करना वगैरह भी थायह logic कुछ ऐसा है कि seatbelt लगाने पर भी सिर टकरा सकता है, इसलिए seatbelt बस झंझट है और उसे इस्तेमाल नहीं करना चाहिए
extreme high-performance code में data structures और algorithms से लेकर memory allocation strategy तक बदल जाती है, और TigerBeetle इस बात के लिए प्रसिद्ध है कि वह start होते समय सारी memory एक साथ allocate कर लेता है
Roc compiler भी Zig जैसा trade-off चुनना चाहता है, इसलिए कई common patterns दिखना स्वाभाविक है
ReleaseSaferuntime 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... में संक्षेप में लिखा है
यह 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 करने का निर्णय लेते हैं—यह भी जानना चाहूंगा
Zig का incremental build निश्चित रूप से निर्णायक feature है, और short term में इसे पाने के लिए language बदलने का फैसला समझ में आता है
लेकिन medium term में उम्मीद है कि Rust में भी निकट भविष्य में ऐसी ही functionality जुड़ जाएगी
speed चाहिए, लेकिन तेजी में अपने ही पैर पर कुल्हाड़ी नहीं मारना चाहता; मैं Rust की safety, Zig की features, और garbage collection के बिना Go runtime को मिलाकर एक language खुद बना रहा हूं
यह ज्यादा feasible लगता है, और user space में implement करना भी संभव दिखता है
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 भी साथ में बेहतर होंगी
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 के तौर पर देना बेहतर होगा
हालांकि cache garbage collection ज़रूरी है, और इसे आसान बनाने के लिए intermediate build artifacts के नए layout पर काम final stage में है
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 करना चाहती है—यह भी जानना चाहता हूं
compile time बहुत ज़्यादा underestimated factor है। C++ build के लिए 10-10 मिनट इंतज़ार करना मेरी सबसे बड़ी शिकायत है और यह developer flow को पूरी तरह तोड़ देता है
.rsfile से.tsfile पर बदलने पर ऐसा difference महसूस होता है जैसे computer ही बदल दिया होZig को सीधे इस्तेमाल नहीं करता, लेकिन कुछ possibilities को लेकर काफी उम्मीद है
C से ज़्यादा convenient Zig में लिखे नए games, TigerBeetle में पहले ही potential दिखा चुका distributed software, और personally मेरी रुचि वाले robotics field को लेकर खास उम्मीद है