10 पॉइंट द्वारा GN⁺ 2025-08-28 | 3 टिप्पणियां | WhatsApp पर शेयर करें
  • Rust अपनी मज़बूत safety guarantees के कारण बड़े codebase में भी refactoring भरोसे के साथ करने देता है, जिससे productivity और maintainability बढ़ती है
  • asynchronous scheduling से जुड़े bugs को compiler पहले ही पकड़ लेता है, और undefined behavior को रोककर stability मजबूत करता है
  • TypeScript जैसी भाषाओं में loose type system की वजह से asynchronous bugs अक्सर production environment में जाकर सामने आते हैं
  • Rust का type system code changes के असर को साफ़ बताता है, जिससे complex projects में confidence और experimentation की इच्छा बढ़ती है
  • Zig, Rust के विपरीत, error handling में loose checking के कारण typo से पैदा हुए bugs मिस कर सकता है, इसलिए reliability कम होती है

सारांश और पृष्ठभूमि

  • Lubeno का backend 100% Rust में लिखा गया है, और codebase इतना बड़ा हो चुका है कि उसे पूरा दिमाग में रखना मुश्किल हो गया है
    • बड़े projects में आमतौर पर changes के side effects समझना कठिन होता है, जिससे productivity में गिरावट आती है
  • Rust की safety guarantees code बदलते समय उसके असर को स्पष्ट बताती हैं, जिससे refactoring का डर कम होता है
    • यह लंबे समय की maintainability और productivity बढ़ाने में मदद करता है
  • यह लेख उस उदाहरण से शुरू होता है जहाँ Rust compiler ने एक asynchronous bug पकड़ा, और फिर Rust के productivity benefits को explore करता है

Rust की safety guarantees का उदाहरण

  • समस्या की स्थिति: concurrent access के लिए एक struct को mutex में wrap किया गया, lock लेने के बाद asynchronous काम किया गया
    let lock = mutex.lock();  
    db.insert_commit(commit).await;  
    
  • समस्या का पता चलना: rust-analyzer ने error नहीं दिखाया, लेकिन router definition file में compile error आया
    .route("/api/git/post-receive", post(git::post_receive))  
                                         ^^^^^^^^^^^^^^^^^  
    error: future cannot be sent between threads safely  
    
  • कारण विश्लेषण:
    • web framework हर HTTP connection के लिए asynchronous task बनाता है, और task scheduler उन tasks को threads के बीच move करता है
    • mutex में lock उसी thread पर release होना चाहिए, लेकिन .await point पर thread बदलने से undefined behavior हो सकता है
    • Rust compiler lock की lifetime track करता है और यह पहचान लेता है कि वह किसी दूसरे thread पर release हो सकता है
  • समाधान: .await से पहले lock release करें
  • महत्त्व: Rust ऐसे asynchronous bugs को compile time पर रोक देता है जिन्हें development environment में reproduce करना मुश्किल होता है

TypeScript के साथ तुलना

  • समस्या की स्थिति: TypeScript code में asynchronous redirect bug हुआ
    if (redirect) {  
        window.location.href = redirect;  
    }  
    let content = await response.json();  
    if (content.onboardingDone) {  
        window.location.href = "/dashboard";  
    } else {  
        window.location.href = "/onboarding";  
    }  
    
  • समस्या का कारण:
    • window.location.href तुरंत redirect नहीं करता, बल्कि उसे schedule करता है, और code execution चलता रहता है
    • race condition की वजह से अनचाहा redirect हो सकता है
  • समाधान: if block में return जोड़ें
    if (redirect) {  
        window.location.href = redirect;  
        return;  
    }  
    
  • सीमा: TypeScript में lifetime tracking या borrowing rules नहीं हैं, इसलिए ऐसे bugs को compile time पर detect नहीं किया जा सकता
    • ये production environment में मिलते हैं और debugging में बहुत समय लगाते हैं

Rust के refactoring फायदे

  • web development में Python, Ruby, JavaScript/Node.js शुरुआती productivity ज़्यादा देते हैं, लेकिन codebase बढ़ने पर loose coupling की वजह से changes मुश्किल हो जाते हैं
    • change के बाद अनपेक्षित errors आते हैं, जिससे code बदलने की इच्छा कम हो जाती है
  • Rust का type system changes के असर को साफ़ बताता है, जिससे refactoring का डर कम होता है
    • उदाहरण: “इस change का असर दूसरे हिस्सों पर पड़ सकता है” जैसी चेतावनी पहले से समस्या रोक देती है
  • codebase बढ़ने पर भी productivity बढ़ती है, क्योंकि existing code reuse किया जा सकता है और changes के समय stability बनी रहती है

testing से तुलना

  • testing refactoring के दौरान regression रोकने में उपयोगी है, लेकिन compiler उसे enforce नहीं करता, इसलिए उसे छोड़ा भी जा सकता है
    • test लिखने में abstraction level, behavior बनाम implementation details, और error prevention जैसे फैसले लेने पड़ते हैं, इसलिए मानसिक बोझ ज़्यादा होता है
  • Rust में compiler आम गलतियों को पहले ही रोक देता है, जिससे testing से जुड़ा decision-making burden कम होता है
    • type system जिन properties को verify नहीं कर सकता, उन्हें tests से पूरा किया जा सकता है

Zig के साथ तुलना

  • Zig Rust जैसी एक system programming language है, लेकिन error handling में अपेक्षाकृत ढीलापन है
    • उदाहरण: error handling code
      const FileError = error{ AccessDenied };  
      fn doSomethingThatFails() FileError!void {  
          return FileError.AccessDenied;  
      }  
      pub fn main() !void {  
          doSomethingThatFails() catch |err| {  
              if (err == error.AccessDenid) {  
                  std.debug.print("Access was denied!\n", .{});  
              }  
          };  
      }  
      
    • AccessDenid typo से bug पैदा होता है, लेकिन Zig compiler उसे number की तरह treat करके compilation सफल होने देता है
  • switch statement का उपयोग करने पर typo detect हो सकता है, लेकिन if statement में उसे नज़रअंदाज़ कर दिया जाता है, जिससे reliability की समस्या पैदा होती है
  • Rust ऐसी design-level कमियों को रोकता है और typo या logical errors की भी सख़्ती से जाँच करता है

निष्कर्ष

  • Rust अपनी safety guarantees और सख़्त type system के कारण बड़े projects की productivity और stability बढ़ाता है
  • asynchronous bugs जैसे जटिल मुद्दों को भी compile time पर पकड़कर maintenance cost कम करता है
  • TypeScript और Zig के उदाहरण loose checking के जोखिम दिखाते हैं और Rust compiler की सख़्ती के मूल्य को उजागर करते हैं
  • Rust web development में सिर्फ शुरुआती productivity ही नहीं, बल्कि लंबे समय के codebase management के लिए भी एक मज़बूत tool बनकर उभरता है

3 टिप्पणियां

 
taptaps 2025-08-30

जब भी मैं यह देखता हूँ कि लोग कहते हैं, यह सबसे बेहतरीन है, यह बहुत ताकतवर भाषा है!!
तो मुझे लगता है, शायद जितना हम सोचते हैं उससे भी कम Rust developers हैं, इसलिए क्या वे लोगों को Rust करने के लिए लुभा रहे हैं??

 
colus001 2025-08-29

क्या सिर्फ मुझे ही Rust से जुड़े सुझाए गए लेख खाने-पीने वाले शो के "चखिए! चखिए!" जैसे लगते हैं?

 
GN⁺ 2025-08-28
Hacker News राय
  • पिछले साल मैंने Rust में लिखे गए virtio-host नेटवर्क ड्राइवर को पोर्ट किया। बैकएंड, interrupt mechanism का स्विच, और लाइब्रेरी से standalone process में बदलाव किया। यह memory mapping, VM interrupt, network sockets और multithreading तक संभालने वाला एक जटिल प्रोग्राम था। मुझे Rust का लगभग कोई अनुभव नहीं था, और virtio का भी कम अनुभव था, लेकिन जब तक प्रोजेक्ट compile हुआ, तब तक वह पूरी तरह काम कर रहा था। Drop से जुड़ा एक bug छोड़कर, जिसे आसानी से ठीक कर लिया गया। मुझे लगता है Rust की libraries इस तरह बनाई गई हैं कि उनका गलत इस्तेमाल करना मुश्किल हो, और इससे बहुत मदद मिली

    • मैं लंबे समय से Rust में development कर रहा हूँ, और ज़्यादातर मामलों में अगर code compile हो जाए तो वह ठीक से काम भी करता है। कभी-कभी deadlock या ordering से जुड़े bugs आते हैं, लेकिन मूल रूप से compile सफल होना यह दर्शाता है कि प्रोजेक्ट का बड़ा हिस्सा सही चल रहा है
  • मुझे Rust शानदार लगता है। लेकिन href assignment bug को TypeScript की गलती मानने वाली राय से मैं सहमत नहीं हूँ। असली मुद्दा यह है कि href सेट करने पर page navigation तुरंत नहीं होता, बल्कि बाद में process होता है। Rust में भी बिल्कुल वही समस्या हो सकती है। अगर Rust में कोई set_href function हो और उसका behavior बाद में process होता हो, तो नीचे जैसा code संभव होगा:

    set_href('/foo')

    if (some_condition) { set_href('/bar') }

    मुझे नहीं लगता Rust में इसे इस तरह design किया जाएगा। setter में action होना अच्छी library design नहीं है, और href assignment के साथ ही page navigation न होना अजीब है। Rust standard library में ऐसी बेवकूफी भरी implementation नहीं होगी। यह Rust vs TypeScript का मामला नहीं, बल्कि Rust standard library और Web Platform API के अंतर का मामला है। मैं इस बात से सहमत हूँ कि Rust ऐसा user experience नहीं देगा

    • औपचारिक रूप से कहें तो, setter में तुरंत action होना वांछनीय design नहीं है। नाम भी navigate_to(href) जैसा होना चाहिए। browser environment में JS code पूरा callback के रूप में चलता है और event loop द्वारा नियंत्रित होता है, इसलिए तुरंत action न होना भी स्वाभाविक है

    • Rust का उदाहरण दिलचस्प है, लेकिन सिर्फ TypeScript के उदाहरण से यह नहीं पता चलता कि TS बड़े प्रोजेक्ट्स के लिए उपयुक्त है या नहीं। Ruby में मुझे अक्सर runtime पर bugs पकड़ने पड़ते हैं, इसलिए असहजता रहती है, लेकिन आखिरकार commit से पहले चीजें ठीक चलें और code पढ़ना व बदलना आसान हो, तो संतोषजनक लगता है। location movement issue JavaScript की समस्या है, और TS ने वही विरासत में पाया है। JS properties को मनमाने ढंग से mutate करने देता है, इसलिए यह हुआ। लेकिन page तुरंत गायब भी नहीं होता, इसलिए एक बार behavior पता हो तो यह तर्कसंगत लगता है

    • तकनीकी रूप से Rust में set_href के () या ! return करने के आधार पर अर्थ को अधिक स्पष्ट संकेत दिया जा सकता है। लेकिन conditional redirect में भी गलत इस्तेमाल पकड़ना अभी भी कठिन रहेगा

    • मेरा आशय यह था कि Rust के ownership model में API को इस तरह design किया जा सकता है कि window.set_href('/foo') कॉल करते समय window की ownership ले ली जाए, जिससे उसे दोबारा कॉल करना असंभव हो जाए। TypeScript में lifetime tracking जैसी कोई अवधारणा ही नहीं है, इसलिए यह संभव नहीं। JS API पहले से मौजूद है, इसलिए TypeScript की ओर से ownership system लाने का भी कोई तरीका नहीं है। मैं यह दिखाना चाहता था कि Rust की कई विशेषताएँ मिलकर अधिक मजबूत guarantees देती हैं

    • तुम्हारी बात का आधार आखिरकार ऐसा लगता है कि “Rust programmers बेहतर होते हैं”, और यही वजह है कि Rust बेहतर है। मुझे नहीं लगता Rust programmers ऐसा circular argument करेंगे

  • assignment के बाद code तब तक चलता रहता है जब तक explicit early return न किया जाए। सच कहूँ तो समझ नहीं आता कि कोई क्यों सोचेगा कि value assignment script execution रोक देगा। TS उदाहरण में context की कमी हो सकती है, लेकिन इसे "data race" कहना अजीब उदाहरण है

    • window.location.href को value assign करने पर browser उस link पर navigate करने का side effect करता है। यह behavior अप्रत्याशित है, और साधारण assignment के कारण नया page load होना execve जैसा लगता है, इसलिए यह सोचना अजीब नहीं कि JS execution तुरंत रुक जाएगा। programming करते समय ऐसी धारणा पर निर्भर नहीं होना चाहिए, लेकिन behavior खुद ही इतना अजीब है कि भ्रम हो सकता है

    • कोई ऐसा सोचे या न सोचे, ऐसे bug में जब कोई बता दे तो fix साफ हो जाता है। लेखक की असली बात यह थी कि TS ऐसे bugs को पकड़ नहीं पाता, और व्यवहार में उन्हें ढूँढना कठिन और समय लेने वाला हो सकता है

    • exit(), execve() आदि वास्तव में execution तुरंत रोक देते हैं, इसलिए redirect को भी वैसा व्यवहार मान लेना स्वाभाविक हो सकता है

    • सिर्फ इसलिए आपत्ति करना कि किसी ने अपना अनुभव साझा किया, अजीब है

    • इस assignment का एक बड़ा side effect है: page छोड़ देना। इसे तुरंत असर करने वाली asynchronous action समझना भी बहुत अनुचित नहीं है। मैंने भी कभी ऐसी धारणा बनाई थी

  • यह मूलतः एक developer की कहानी है कि उसने static type system की उपयोगिता समझी। ऐसे लेख हर बार मज़ेदार लगते हैं

    • मैं अपने ब्लॉग में यह दिखाना चाहता था कि Rust की lifetime tracking और trait system सिर्फ साधारण type mismatch पकड़ने से कहीं अधिक जटिल issues भी पकड़ सकते हैं। TypeScript भी static typed language है, लेकिन Rust जितनी guarantees नहीं दे सकती
  • क्या ज़्यादातर फायदे आखिरकार static typing, यानी compiled language इस्तेमाल करने से नहीं आते? Java, Go, C++ में भी ऐसा ही है। TypeScript में कुछ tricks हैं; यह JS में compile होती है और JS की समस्याएँ भी साथ लेती है, लेकिन फिर भी उपयोगी है। Rust का type system अधिक सख्त है, इसलिए अतिरिक्त compile-time checks मिलते हैं, लेकिन उसी अनुपात में इसे सीखना और पढ़ना कठिन भी लगता है

    • कुछ हद तक सहमत हूँ, लेकिन Rust के type system में ownership, shared/exclusive access, thread safety, sum type जैसी और भी dimensions हैं। ownership/borrowing system की वजह से यह स्पष्ट हो जाता है कि argument एक अस्थायी view है या पूरी ownership का transfer। बड़े प्रोग्रामों या external libraries के साथ यह बहुत फायदेमंद है। उदाहरण के लिए Go का slice type runtime पर कौन-सी operations अनुमति देता है, यह साफ नहीं बताता, और read-only borrow जैसा कुछ करना भी अस्पष्ट है। Rust type system के स्तर पर thread safety guarantee कर सकता है, इसलिए दूसरी भाषाओं में runtime पर मुश्किल से मिलने वाले data races को compile-time पर ही रोक देता है

    • सभी static typed languages को एक साथ रखकर देखना इस बात का संकेत है कि आपने union(sum) types और pattern matching की असली ताकत अभी महसूस नहीं की है। एक बार union types की आदत पड़ जाए, तो पारंपरिक static typed languages कम संतोषजनक लगने लगती हैं

    • एक बड़ा फायदा traits/impl traits है। Rust में C# के Extension Method की तरह किसी भी type पर बाद में trait जोड़ा जा सकता है। ज़्यादातर भाषाओं में type जब library में define हो जाता है, तो वह वहीं तय हो जाता है; लेकिन Rust में साधारण types पर भी धीरे-धीरे लगातार functionality जोड़ी जा सकती है। यह late-bound प्रकृति type system में dynamism लाने वाला तत्व है। थोड़ा बढ़ा-चढ़ाकर कहें तो Rust की असली superpower borrow checker से ज़्यादा उसके type system की openness और flexibility है। सब कुछ शुरू में ही design करने की ज़रूरत नहीं, आप इसे क्रमिक रूप से बढ़ा सकते हैं

    • सिर्फ static typed language होने से सब समान प्रभाव नहीं देते। Java आखिरकार Object और runtime casting पर निर्भर करता है। Go में enum नहीं है। C++ में variant जैसी अवधारणा जुड़ी है, लेकिन उसे सुरक्षित ढंग से इस्तेमाल करने के लिए try/except जैसी manual handling करनी पड़ती है, इसलिए संरचनात्मक रूप से असुविधाजनक है

    • लोग कहते हैं Rust सीखना कठिन है, लेकिन अगर आप इसे सही तरह से सीख लें तो यह इतना कठिन नहीं है। शुरुआत में coding का एक हिस्सा बस कुछ जोड़-तोड़ कर चीज़ें चलवा लेना होता है, और Rust उस शैली के लिए अनुकूल भाषा नहीं है। मैं इसे शुरुआती लोगों के लिए recommend नहीं करूँगा, लेकिन इसे पढ़ना मुश्किल नहीं है

  • Rust की मजबूत safety की वजह से codebase को छूते समय आत्मविश्वास बढ़ता है। इसी आत्मविश्वास के कारण core हिस्सों का refactoring भी डरावना नहीं लगता, और नतीजतन productivity और maintainability दोनों बहुत बेहतर हो जाते हैं। लेकिन यही काम tests के लिए भी कहा जा सकता है। अगर tests न हों तो सख्त compiler बहुत मदद करेगा, लेकिन यदि tests अच्छे लिखे गए हों, तो किसी भी language में आत्मविश्वास के साथ refactor किया जा सकता है

    • जहाँ संभव हो, compiler का चीज़ों को statically prove करना बेहतर है। tests का उपयोग उन स्थितियों में होना चाहिए जहाँ static guarantees कठिन हों। आदर्श अंतिम स्तर formal verification है, लेकिन व्यवहार में वह बहुत कठिन है, इसलिए यह सामान्य सलाह भले न हो, सिद्धांत के स्तर पर सही है

    • अच्छे tests और अच्छी तरह उपयोग किया गया type system दोनों bugs पकड़ने में प्रभावी हैं। लेकिन tests लिखना कभी-कभी xkcd की "Standards" कॉमिक याद दिलाता है। जैसे standards की समस्या सुधारने के लिए एक और standard बना देते हैं, वैसे ही bugs पकड़ने के लिए और code लिखते हैं। फिर भी type system का रखरखाव language designer करता है, इसलिए हर project को अलग से इसकी देखभाल नहीं करनी पड़ती

    • हर बार code refactor करने पर tests को भी refactor करना पड़े, तो काम दोगुना हो जाता है

  • मुझे लगता है Rust और F# के type systems code refactoring के समय सबसे ज़्यादा चमकते हैं। "fearless refactoring" सचमुच सटीक अभिव्यक्ति है

    • एक कमी यह है कि Rust अधूरे code को बर्दाश्त नहीं करता, इसलिए refactoring के दौरान 'आंशिक रूप से काम करने वाला code' रखना संभव नहीं होता। या तो पूरा करो, या बिल्कुल मत करो। इसलिए experimental code लिखने में असुविधा होती है। लेकिन यही सख्ती अंततः अच्छे code की ओर ले जाने वाला कारण भी है
  • Zig का उदाहरण चौंकाने वाला है। वह इतना अस्थिर लगता है कि समझ नहीं आता कोई उस design को अच्छा कैसे मान सकता है

    • मुझे लगता है यह शायद bug है। लेकिन Zig जैसी creator-driven language में bug fix होने के लिए यह ज़रूरी है कि creator भी उसे bug माने। अगर उसे intentional माना गया, तो वही design चलता रह सकता है

    • हर language में कुछ न कुछ अस्थिर design होता है। उदाहरण के लिए Go या Zig में mutex.unlock() हमेशा explicit करना पड़ता है, scope से बाहर जाने पर auto-release नहीं होता। दूसरी ओर Rust के as operator की तरह number types के बीच conversion आसान है, और इसी वजह से मैंने पूरे दिन bugs ढूँढे हैं

    • पहले मैंने वह error देखा ही नहीं था, यह comment पढ़कर पता चला

    • क्या linter system के भीतर मौजूद न होने वाले error references पकड़कर, switch इस्तेमाल करने की सिफारिश जैसी warning नहीं दे सकता?

    • मैं समझता था कि error set function signature के आधार पर बनता है। थोड़ा अनोखा है

  • मुझे यह पसंद है कि मजबूत और sound static type system विभिन्न तरह की क्षमताएँ देता है। मेरा भी 10 लाख SLOC वाले Haskell codebase में बड़े refactoring को आसानी से करने का अनुभव रहा है। उन्नत features के बिना भी, सिर्फ type system के सहारे यह संभव था

  • Rust ने यह सही पकड़ा कि await boundary पर lock पकड़े रखना समस्या हो सकती है, लेकिन await से पहले उस lock को छोड़ना वास्तव में सुरक्षित है या नहीं, इसके लिए अतिरिक्त context चाहिए। मुझे लगता है lock transaction commit बनने तक पकड़े रहना चाहिए; अगर await से पहले छोड़ दिया जाए तो concurrency issue हो सकता है। मुझे Rust async की गहरी जानकारी नहीं है, लेकिन commit के बाद join या select जैसी किसी चीज़ से रोकना चाहिए, ऐसा लगता है

    • अगर await के दौरान lock पकड़े रखना ज़रूरी है, तो async-aware mutex इस्तेमाल किया जा सकता है। futures या tokio crate ऐसे locks implement करते हैं। इनका उपयोग आमतौर पर तब होता है जब lock लंबे समय तक पकड़े रखना हो या await के बीच भी बनाए रखना हो। इनकी cost सामान्य lock से ज़्यादा होती है

    • अगर await boundaries के पार lock बनाए रखना ज़रूरी हो, तो Tokio का async-aware mutex इस्तेमाल किया जा सकता है। tokio/sync/struct.Mutex docs देखें