जो अच्छा रहा
- rewrite छोटे चरणों में किया गया (incremental, stop-and-go), यह अच्छी तरह काम करता रहा, और नया कोड पढ़ने व समझने में आसान हो गया
- पूरे कोड को एक साथ देखने का दृष्टिकोण मिला, जिससे performance optimization के अवसर मिले
- लगभग 1/3 से 1/2 तक unused code हटा दिया गया। Rust या Go जैसी modern programming languages dead code को बेहतर ढंग से पहचानती हैं और developers को बताती हैं
- out-of-bounds access या overflow/underflow की चिंता नहीं रही
- built-in test framework बहुत उपयोगी है
- CMake files हटाकर खुशी हुई
जो अच्छा नहीं रहा
अब भी undefined behavior को ट्रैक करना पड़ता है
- C/C++ से Rust में incremental rewrite करते समय बहुत सारे raw pointers और
unsafe{} blocks का उपयोग करना पड़ा
- Rust के नियम
unsafe के भीतर भी लागू होते हैं, लेकिन compiler उन्हें check नहीं करता, इसलिए undefined behavior आसानी से हो सकता है
unsafe के भीतर कई read-only pointers XOR एक mutable pointer वाले नियम को आसानी से तोड़ा जा सकता है
- Miri इसे पकड़ने वाले रक्षक की तरह काम करता है
Miri हमेशा काम नहीं करता और अब भी Valgrind का उपयोग करना पड़ता है
- अगर cryptography libraries की तरह C या assembly में लिखे हिस्सों वाली libraries का उपयोग करें, तो Miri काम नहीं करता
- काफी
unsafe code ऐसा है जिसे Miri check नहीं कर पाता
- कुछ tests को
valgrind में चलाना पड़ा
अब भी memory leaks को ट्रैक करना पड़ता है
- C API का आम pattern यह है कि
MYLIB_init() में memory allocate होती है और MYLIB_release() में free होती है, लेकिन MYLIB_release को call करना भूल जाना आसान है
- Rust developers RAII के साथ wrapper objects बनाना चाहते हैं, लेकिन C API का उपयोग करने वाले tests में यह सुविधा इस्तेमाल नहीं की जा सकती
- जटिल logic में cleanup function को हर बार call करना कठिन होता है। C में इसे
goto से हल किया जाता है, लेकिन Rust इसे support नहीं करता
defer crate से समाधान मिला, लेकिन borrow checker को यह पसंद नहीं आया
cross-compilation हमेशा काम नहीं करती
- Miri की तरह, अगर C या assembly में implement किए गए हिस्सों वाली libraries का उपयोग करें, तो
cargo build --target=... सीधे काम नहीं करता
Cbindgen हमेशा काम नहीं करता
- Cbindgen का इस्तेमाल Rust codebase से C headers generate करने के लिए बहुत होता है, लेकिन इसकी सीमाएँ और bugs हैं
unstable ABI
Option जैसे उपयोगी standard library types के लिए stable ABI नहीं है, इसलिए repr(C) annotation के साथ इन्हें manually duplicate करना पड़ता है
custom memory allocator support की कमी
- बहुत-सी C libraries users को runtime पर allocator देने की सुविधा देती हैं। Rust में केवल compile time पर global allocator चुना जा सकता है
- resource cleanup की समस्या arena allocator से हल हो सकती है, लेकिन Rust में यह idiomatic नहीं है और standard library के साथ integrate नहीं होती
complexity
- FFI handling के लिए
UnsafeCell, RefCell, MaybeUninit, Pin जैसी चीज़ें इस्तेमाल करनी पड़ती हैं, जिससे complexity बढ़ जाती है
- pure Rust अपने आप में ही जटिल है, और जब उस पर FFI layer भी जुड़ जाए तो यह एक दैत्य बन जाता है
- Rust की complexity की वजह से कुछ developers ने इस codebase पर काम करने से मना भी किया
निष्कर्ष
- कुल मिलाकर Rust rewrite से संतुष्टि है, लेकिन कुछ क्षेत्रों में निराशा हुई, और इसमें उम्मीद से कहीं अधिक मेहनत लगी
- C के साथ बहुत अधिक interaction करने वाला Rust, pure Rust से पूरी तरह अलग भाषा जैसा लगता है। इसमें बहुत friction है और कई traps हैं। C++ की जिन समस्याओं को Rust हल करने का दावा करता है, उनमें से कई वास्तव में बिल्कुल हल नहीं होतीं
- Rust, Miri, cbindgen आदि के developers के प्रति गहरा आभार। उन्होंने बहुत बड़ा काम किया है। इसके बावजूद, जब C FFI बहुत अधिक करना पड़े, तो भाषा और tools अपरिपक्व लगते हैं, लगभग v1.0 से पहले जैसे
- अगर भविष्य में
unsafe की ergonomics, standard library, documentation, tools और unstable ABI में सुधार हो, तो अनुभव अधिक सुखद हो सकता है
- लगता है Microsoft और Google ने भी इन सभी बातों को महसूस किया है, इसलिए वे इस क्षेत्र में वास्तविक funding निवेश कर रहे हैं
- अगर आप अभी Rust नहीं जानते, तो आपका पहला project pure Rust में होना चाहिए और FFI विषयों से दूर रहना बेहतर है
- शुरुआत में इस rewrite के लिए Zig या Odin का उपयोग करने पर विचार किया गया था, लेकिन corporate production codebase में v1.0 से पहले की language का उपयोग नहीं करना चाहते थे। अब यह सोचने का मन होता है कि क्या अनुभव सचमुच Rust से भी बुरा होता। शायद Rust का model, C model (या C++ model) के साथ वास्तव में मेल नहीं खाता, इसलिए दोनों को साथ इस्तेमाल करने पर friction बहुत अधिक हो जाता है
- अगर भविष्य में फिर ऐसा काम करना पड़े, तो Zig पर गंभीरता से विचार करेंगे। जब भी कोई कहे, "बस Rust में rewrite कर दीजिए", तो उन्हें यह लेख दिखाइए और पूछिए कि क्या उनका मन बदल गया है
12 टिप्पणियां
Zig भले ही अभी pre v1 हो, फिर भी यह कई C libraries का उपयोग कर सकता है, इसलिए यह उम्मीद से ज़्यादा काम का है। किसी चल रहे C-आधारित प्रोजेक्ट पर कुछ जोड़ने के लिए Rust की तुलना में Zig बेहतर विकल्प हो सकता है।
जब मैंने Rust को देखना शुरू किया,
unsafeनाम का keyword देखते ही एक अजीब-सा झटका लगा...मुझे नहीं लगता कि Rust, C++ की पुरानी और जड़ जमाई समस्याओं को हल कर सकता है। यह बात syntax के नज़रिए से ज़्यादा, practical नज़रिए से है.
इसके कारण हैं:
पहले से ही बहुत सारे production systems C/C++ का इस्तेमाल कर रहे हैं। और वे स्थिर रूप से अच्छी तरह चल भी रहे हैं। उनमें से ज़्यादातर को बेवजह Rust में port करने की कोशिश नहीं की जाती।
मूल रूप से hardware को reference count मानकर नहीं बनाया जाता। C/C++ का इस्तेमाल कई बार hardware, OS, driver, और binary स्तर को तेज़ी से नियंत्रित करने के लिए किया जाता है, लेकिन Rust को support करने के लिए low-level developer को आखिरकार
unsafeका इस्तेमाल करते हुए resource lifecycle को सीधे manage करना पड़ता है, और यह भी एक बड़ी लागत है।मेरा मानना है कि लेखक का अनुभव, भाषा के संभावित मूल्य और सैद्धांतिक चर्चा से भी अधिक महत्वपूर्ण है।
वास्तव में, जिन क्षेत्रों में C/C++ स्तर की भाषा की ज़रूरत होती है, वहाँ resource management का स्तर ऐसा है कि उसे Rust से बदलना कुछ अधूरा-सा और असुविधाजनक विकल्प लगता है।
यह लेख भी Rust को गलत समझकर शुरू किया गया लगता है.
सामग्री देखें तो यह शायद ऐसी library है जिसे Rust के बाहर की चीज़ों से बार-बार communicate करना पड़ता है, और उस बिंदु पर गड़बड़ होना लगभग तय है... शुरुआत से ही native language में कोई भी चीज़ पूरी तरह साफ-सुथरी नहीं होती, और Rust ने भाषा स्तर पर उसे सुरक्षित तरीके से wrap किया है, इसलिए जैसे-जैसे भाषा के बाहर के सिस्टमों से संपर्क बढ़ता है, उसका फायदा काफ़ी कम हो जाता है.
कुछ हद तक यह सही है, लेकिन मूल लेख के development environment में उनका हल न होना लगभग तय था. मुझे लगता है कि Rust को किसी रामबाण इलाज की तरह मानकर अप्रोच करना ही समस्या थी.
मुझे ऐसा लगा कि C/C++ को Rust में धीरे-धीरे बदलने की प्रक्रिया में
unsafeका इस्तेमाल करना ही पड़ेगा, इसलिए Rust में बदलने का खास मतलब नहीं है। Rust में क्रमिक रूप से बदलने के बजाय मैं Zig चुनूँगा — मेरा मतलब यही था। लेकिन मुख्य लेख में कहाँ लिखा है कि यह ऐसी लाइब्रेरी है जिसे Rust के बाहर के साथ बार-बार संचार करना पड़ता है?FFI का इस्तेमाल करने का मतलब ही है कि Rust के बाहर की दुनिया से कम्युनिकेशन किया जा रहा है.
और अगर मूल लेख की सामग्री देखें, तो यह सिर्फ किसी state या सरल data के आदान-प्रदान तक सीमित नहीं लगता, बल्कि ऐसा दिखता है कि अंदरूनी और बाहरी हिस्से जटिल तरीके से एक-दूसरे के साथ interact कर रहे हैं.
अगर C में लिखी गई लाइब्रेरी को धीरे-धीरे Rust में बदलना है, तो FFI से बचना तो संभव नहीं है, है न? प्रोग्राम के छोटे-छोटे हिस्सों को Rust में बदलना होगा और बाकी C हिस्सों को FFI से संभालना पड़ेगा, तो क्या आपने इन कामों को बाहरी संचार कहा है? अगर ऐसा है, तो मुझे लगता है कि मूल लेखक का Rust को लेकर संशय महसूस करना स्वाभाविक हो सकता है। जब तक पूरा कोड एक ही बार में नहीं बदला जाता, Rust के फायदे मिलते नहीं, इसलिए शायद वह Zig की सिफारिश करते हैं।
^-^
क्योंकि source code में
unsafeहिस्से स्पष्ट रूप से दिखाए जाते हैं, इसलिए मैंने सोचा था कि जब तक program entry point सेunsafeblock का इस्तेमाल न किया जाए, तब तक FFI के प्रभाव-क्षेत्र की पूरी पहचान की जा सकती है, और यह उपयोगी होगा। लेकिन लगता है कि लेखक को यह बात ज़्यादा असरदार नहीं लगी।असल में, जिस पल आपने FFI का इस्तेमाल किया, उसी पल से सुरक्षित डिज़ाइन की उम्मीद लगभग खत्म हो गई थी।
बिल्कुल।
सही कहा,
unsafeको हर जगह भर देने की बात इतनी बेधड़क लिखी है, और फिर भी कहते हैं कि समस्या हल नहीं हुई...