Rust का मूल
(jyn.dev)- Rust एक ऐसी भाषा है जिसमें कई कॉन्सेप्ट एक-दूसरे से गहराई से जुड़े हुए हैं, इसलिए बेसिक प्रोग्राम को समझने के लिए भी कई चीज़ें एक साथ सीखनी पड़ती हैं
- function, generic, enum, pattern matching, trait, reference, ownership,
Send/Sync,Iteratorआदि सभी परस्पर interaction को ध्यान में रखकर डिज़ाइन किए गए मुख्य तत्व हैं - JavaScript की तुलना में, JS में कुछ कॉन्सेप्ट जानकर भी कोड लिखा जा सकता है, लेकिन Rust में भाषा के पूरे संदर्भ को समझने पर ही वास्तव में अर्थपूर्ण कोड लिखना संभव होता है
- Rust की यही जटिलता सीखने की बाधा को बढ़ाती है, लेकिन साथ ही safety और consistency भी देती है, और कोड डिज़ाइन करने के तरीके पर बड़ा प्रभाव डालती है
- ऐसी भाषाई संरचना Rust को खास बनाती है, और “smaller Rust” की विज़न हमें सटीक रूप से जुड़े हुए language philosophy पर फिर से सोचने के लिए प्रेरित करती है
Rust सीखने की कठिनाई
- Rust में entry barrier ऊँचा है, फिर भी बहुत से लोगों ने documentation, API, diagnostic improvements में योगदान दिया है
- बुनियादी कॉन्सेप्ट में first-class functions, enum, pattern matching, generic, trait, reference, borrow checker, concurrency safety, iterator आदि शामिल हैं
- ये कॉन्सेप्ट एक-दूसरे पर निर्भर और गहराई से जुड़े हुए हैं, इसलिए इन्हें एक-एक करके अलग से सीखना कठिन है, और standard library भी अधिकांशतः इन्हीं फीचर्स का उपयोग करती है
- लगभग 20 लाइनों के Rust कोड को समझने के लिए भी functional paradigm,
Resultऔर error handling, generic types, enum, iterator जैसे कई तत्वों को एक साथ समझना पड़ता है
Rust और JavaScript की तुलना
- जब एक ही file change detection प्रोग्राम Rust और JS में लिखा जाता है, तो Rust में भाषा के कई कॉन्सेप्ट आपस में गुँथे हुए दिखाई देते हैं
- JS में मूल रूप से सिर्फ function और null handling समझकर भी काम करने वाला कोड लिखा जा सकता है
- इसका मतलब यह नहीं कि Rust बस अधिक कठिन है, बल्कि यह दिखाता है कि Rust ऐसा डिज़ाइन है जो पूरी भाषा की संरचनात्मक समझ की माँग करता है
Rust का परस्पर जुड़ा हुआ डिज़ाइन
- Rust का मूल ऑर्गेनिक तरीके से डिज़ाइन किए गए फीचर्स के संयोजन में है
- enum, pattern matching के बिना असुविधाजनक है, और pattern matching भी enum के बिना सीमित हो जाती है
ResultऔरIteratorको generic के बिना implement करना संभव नहीं हैSend/Syncकॉन्सेप्ट औरprintlnकी constraints को trait के बिना सुरक्षित रूप से व्यक्त नहीं किया जा सकता- borrow checker, closure capture analysis के माध्यम से
Send/Syncsafety की गारंटी देता है
- यह परस्पर जुड़ाव Rust को सिर्फ फीचर्स के संग्रह के बजाय एक एकीकृत language system बनाता है
छोटे Rust की विज़न
- 2019 में without.boats ने “Smaller Rust” का ज़िक्र करते हुए छोटे और परिष्कृत Rust की संभावना पर चर्चा की थी
- आज Rust बहुत बड़ा हो चुका है, लेकिन छोटे Rust की अवधारणा अब भी सटीक रूप से आपस में फिट होने वाले language design के सार की याद दिलाती है
- Rust का आकर्षण इस बात में है कि भाषाई तत्व आपस में स्वतंत्र होते हुए भी, साथ आने पर मज़बूत expressiveness और safety प्रदान करते हैं
निष्कर्ष
- Rust सीखना कठिन है, लेकिन आपस में जुड़े कॉन्सेप्ट की consistency और integration इसकी बड़ी ताकत है
- इसी संरचना की वजह से Rust डेवलपर को सिर्फ कोड लिखने तक सीमित नहीं रखता, बल्कि safety और performance दोनों को साथ लेकर सोचने का तरीका विकसित करता है
- Rust का सार “छोटी लेकिन सटीक मुख्य भाषा” में है, और यह आज के विस्तारित Rust में भी एक महत्वपूर्ण philosophy बना हुआ है
1 टिप्पणियां
Hacker News राय
fs.watchके docs में साफ़ लिखा है कि callback मेंfilenamenullहो सकता है, इसलिए इसे ज़रूर check करना चाहिए। Rust में यह बात type system में दिखती और आपको इसे अनिवार्य रूप से handle करना पड़ता, लेकिन JS में कोड को ढीले-ढाले तरीके से लिखना आसान है। संबंधित docsnullcheck enforce होता है। इसलिए मुझे लगता है कि यह एक अच्छा उदाहरण है कि TS, JS में Rust जैसी correctness के काफ़ी करीब पहुँचने वाला, comparatively कम बोझ वाला कदम हैfor path in pathsकी जगहfor (const path of paths)होना चाहिए। JS में parentheses न हों तो तुरंत error आ जाएगा, लेकिनinऔरofका फ़र्क यह है किinvalues नहीं बल्कि indices (iterable index) पर iterate करता है, इसलिए असल में index string में बदलकरfs.watchके पहले argument में चला जाएगा। TypeScript भी शायद इस गलती को पकड़ न पाएkindकहाँ से आया, यह जानना चाहूँगा।console.log("${kind} ${filename}")मेंkindनहीं,eventType(string) होना चाहिएprintlnसिर्फ़ उन्हीं types को print कर सकता है जोDisplayयाDebugtrait implement करते हैं। इसलिएPathको सीधे print नहीं किया जा सकता। हर OS path को UTF-8 में नहीं रखता, जबकि Rust के string types पूरी तरह UTF-8 हैं। यानीPathको print करने में lossy conversion हो सकता है।Pathकाdisplaymethod ऐसा type लौटाता है जोDisplayimplement करता है। Rust ने इसे type system में समाहित किया है, लेकिन JS/TS में यह बताना मुश्किल है कि internal string UTF-16 है, और non-Unicode path को सही तरह handle करने के लिए सीधेTextEncoder/Decoderइस्तेमाल करने पड़ते हैं। पुराने अनुभव से कहूँ तो जब server Shift_JIS में text भेजता था और उसेresponse.text()से पढ़ते थे, तो runtime में सिर्फ़ empty string मिलती थी। अगर आपको encoding issues की आदत न हो, तो ऐसी स्थिति में debugging में कई दिन निकल सकते हैं। और JS example में ऐसे bugs और syntax errors हैं जो Rust code में नहीं हैं (loop मेंfor-inकी जगहfor-ofचाहिए)। इस example को सिर्फ़ "first-class functions" का मामला कहना ठीक नहीं होगा; Rust की तरह iterators की समझ भी चाहिए, और इसमें CommonJS भी इस्तेमाल हो रहा है। साथ हीasync/await, Promises और top-level await भी अलग से सीखने पड़ते हैं, और top-level await कोnodeसहित कुछ runtimes ने हाल में ही support किया है। अभी भी कुछ JS engines (जैसे React Native का Hermes) में यह supported नहीं हैयही वजह है कि मैं Rust इस्तेमाल करता रहता हूँ। यह example तो बस एक है, लेकिन ऐसी छोटी-छोटी समस्याएँ और pitfalls दूसरी भाषाओं में हर जगह बिखरी रहती हैं। वे अलग-अलग होकर शायद न दिखें, लेकिन पूरे program lifecycle में जमा होकर अजीब bugs पैदा करती रहती हैं, और आपको लगातार उन्हें ढूँढना पड़ता है। Rust में ऐसा नहीं होता। इसका type system बेहिसाब मामलों को पहले ही रोक देता है। सच में, जब आप Rust में feature-complete software ship कर देते हैं, तो उसके बाद सिर्फ़ कभी-कभार features जोड़ते हैं और सामान्य bug-fixing का काम लगभग ग़ायब हो जाता है। हाँ, logical bugs कहीं भी हो सकते हैं, लेकिन दूसरी भाषाओं की तरह बेवकूफ़ी भरी type/structure mismatch से पैदा होने वाली समस्याएँ Rust जड़ से रोक देता है, इसलिए productivity और maintainability का अनुभव पूरी तरह अलग होता है
निजी तौर पर मुझे लगता है कि JS/TS में
thenable/Promiseऔरasync-awaitको सच में ठीक से समझने वाले developers बहुत ज़्यादा नहीं हैं। ऐसा भी देखा है:callback-style wrapper को वैसे का वैसा Promise में लपेटकर फिर
asyncfunction के अंदर दोबारा इस्तेमाल किया जाता है। हर बार यह देखकर दिल टूट जाता है। सच में ऐसी code patterns बहुत जगह देखी हैं। और अगर modules केimport, asyncimport(), transpilation, code splitting वगैरह तक सोचें, तो चीज़ें सच में बहुत जटिल हो जाती हैंrustfmt,rust-analyzerसे जुड़े details,rustcके bug fixes, और Cargo की error reporting को बेहतर बनाना। मैं खुद रोज़ issue reproduction scripts लिखने के लिए cargo script इस्तेमाल करता हूँ-Zscriptfeature keyword search करके research शुरू करते-करते भटक गया था। यह 2023 से चल रहा है, और कुछ open issues देखकर लगता है कि यह लगभग पूरा होने के करीब है। ZomboDB repository में भी Rust से build pipeline संभालते देखा, लेकिन पूरा context नहीं समझ पाया। मैं यह भी कहना चाहता हूँ कि cargo frontmatter script portability के लिए बेहद उपयोगी है। सिर्फ़ एक file share करनी होती है, और Python या Node.js की तरह अलग से install/init किए बिना dependencies लेकर तुरंत इस्तेमाल किया जा सकता है#!/some/pathसे शुरू होने वाली file को shell बस दिए गए command के साथ पूरे file content कोstdinके रूप में पास करके चलाता हैasyncऔरconstहैं। अगर बात यह है किasyncऔरconstआने से पहले Rust छोटा और साफ़ था, तो उसे सीधे कहना चाहिए था; अफ़सोस कि लेख में यह इतनी स्पष्टता से नहीं कहा गयाCopytrait, reborrowing, deref coercion, loops में automaticinto_iter, scope ख़त्म होने पर automaticdropcalls (इसे सीधे call भी कराया जा सकता है या compiler error दे सकता है), trait bounds में default:Sized, lifetime elision, match ergonomics जैसी कई automation/convenience चीज़ें हटा दी जाएँ तो एक यांत्रिक रूप से बहुत सरल Rust बन सकता है। लेकिन ऐसी language रोज़मर्रा के काम में बेहद असुविधाजनक होगी। विडंबना यह है कि ऊपर की कई चीज़ें असल में beginners की मदद के लिए बनाई गई थींasyncऔरconstसे पहले वाला Rust छोटा और साफ़ था। मैंने इसे सीधे इसलिए नहीं कहा क्योंकि उन features पर काम करने वाले कई लोग मेरे दोस्त हैं। Matklad ने lobste.rs पर इसे बहुत अच्छी तरह कहा है। 2015 का Rust ज़्यादा complete और consistent था, लेकिन Rust का vision पूरी coherence नहीं, बल्कि industry में उपयोगी language बनना हैDerefकब और किस क्रम में apply होगा।.into()औरFromtrait type conversions को बहुत छिपे हुए ढंग से संभालते हैं। standard library में भी ऐसी कई "convenience" functions हैं। नतीजा यह होता है कि object का type धुंधला हो जाता है, और function calls को implementations से जोड़कर समझना मुश्किल होता है (हालाँकि IDE मदद करे तो कुछ आसानी होती है)pgrxecosystem सच में अलग दर्जे का है)। फिर भी, C में काम करने से तो कुछ भी बेहतर हैconstkeyword के अर्थ जैसी चीज़ें Rust में सीखना बाद में दूसरी भाषाओं से आई गलत आदतों को छोड़ने की मेहनत भी कम कर सकता हैfsharpदेखने की भी सलाह दूँगाmemmodule pattern का पालन करते हैं, इसलिए interface structure को अच्छे से समझना हो तो std::mem से शुरू करना बेहतर होगा