1 पॉइंट द्वारा GN⁺ 5 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Rust development के दौरान सभी error paths और ownership issues को तुरंत साफ़ करने से implementation flow टूट सकता है, इसलिए पहले happy path बनाना और बचा हुआ काम warnings के रूप में track करना उपयोगी strategy है
  • Standard Rust से ही unwrap, clone, todo!, default compiler warnings, और /// documentation comment trick को मिलाकर अधूरे code को mark किया जा सकता है
  • मौजूदा तरीकों में unwrap और clone warning नहीं देते, todo! भी indirect warnings पर निर्भर करता है, और /// trick location के आधार पर compile error या अनचाही documentation में बदल सकती है
  • wip crate wip!, unwrap_wip, clone_wip, fixme! के ज़रिए temporary implementation, temporary unwrap/clone, और बाद में ठीक करने वाले notes को हमेशा warnings के रूप में दिखाता है
  • Meilisearch CI में -D warning से warnings को errors में बदलकर WIP code को शामिल होने से रोकता है, और development के दौरान जोड़ी गई wip dependency को review से पहले history cleanup के दौरान हटाने का flow इस्तेमाल करता है

Rust development के दौरान warnings की ज़रूरत क्यों पड़ती है

  • Rust correctness को अहमियत देने वाली language है, लेकिन development के दौरान हर error path को तुरंत handle करने से implementation speed और focus कम हो सकता है
  • जब rustc Result return, error handling, edge case implementation जैसी वजहों से compilation रोकता है, तो आम तौर पर दो choices बनती हैं
    • तुरंत पूरा error handling implement करें, और PR ready होने तक कई बार refactor करें
    • TODO later जैसी marking छोड़ें, issue को warning level तक घटाएँ, और मौजूदा काम जारी रखें
  • Warning development को रोकती नहीं है, लेकिन PR merge से पहले साफ़ करने वाला signal बनी रहती है
  • Meilisearch CI में compiler को -D warning flag के साथ चलाता है, जिससे सभी warnings errors में बदल जाती हैं और WIP code गलती से deploy नहीं होता
  • Code review bugs रोकने और team में knowledge share करने की basic practice है, लेकिन सिर्फ़ इंसानी याददाश्त और attention पर निर्भर रहने से आखिरकार mistakes हो सकती हैं, इसलिए additional guardrails की ज़रूरत होती है

Standard Rust से अधूरे काम को टालने के तरीके

  • unwrap से error handling टालना

    • Result या Option पर unwrap इस्तेमाल करने से error path में panic होने की कीमत पर अंदर की value निकालकर मौजूदा implementation जारी रखा जा सकता है
    • बाकी code तैयार होने के बाद temporary रूप से जोड़े गए unwrap को चुनकर हटाया जा सकता है और उचित error handling से बदला जा सकता है
    • यह तरीका इस assumption पर आधारित है कि Rust error को सही तरीके से handle करने की structure देता है, फिर भी उस handling को तुरंत पूरा करना ज़रूरी नहीं है
  • clone से ownership problem टालना

    • Ownership issues error handling से कम common हैं, लेकिन सही ownership बनाए रखना design change मांग सकता है
    • ऐसे में value को move करने के बजाय clone करके lifetime problem को temporary रूप से avoid किया जा सकता है
    • बाद में उचित variables को higher stack में pass करके clone हटाया जा सकता है या नहीं, इसे experience से identify किया जा सकता है
    • हालांकि fix code structure change तक जा सकता है, इसलिए error handling की तुलना में इसे टालना ज़्यादा tricky है
  • todo!() से implementation की जगह reserve करना

    • Rust standard library का todo!() macro अधूरे code को दिखाता है, और prototyping के दौरान type analysis pass कराने के लिए placeholder के रूप में उपयोगी है
    • todo!() panic के साथ diverge करता है, इसलिए इसे ज़्यादातर type positions में placeholder expression के रूप में इस्तेमाल किया जा सकता है
    • Interface define करते समय function definitions को इस्तेमाल करने योग्य रखते हुए actual implementation को बाद के लिए टाला जा सकता है
    • todo!() program users को feature unimplemented होने की जानकारी देने के बजाय, code author और reviewer के लिए WIP convenience feature के ज़्यादा करीब है
    • Users को feature unimplemented state बताने के लिए अलग से unimplemented!() macro है
  • Default compiler warnings और documentation comment trick

    • Rust compiler की default warnings भी development के दौरान अधूरी state दिखाने में उपयोगी हैं
      • Unused bindings
      • Unnecessary mut
      • Unused Result और Option
    • Development के दौरान ऐसी warnings को _ prefix या दूसरी tricks से temporarily remove करने पर अधूरा code वैसे ही आगे निकल जाने का risk होता है
    • Type system से track नहीं होने वाले edge cases या broken logic को कभी-कभी comments में छोड़ा जाता है
    • /// documentation comment जैसा दिखने वाला comment लगाने पर, Rust documentation target न होने वाले documentation comment के बारे में warning देता है, इसलिए उस location की problem को warning के रूप में track किया जा सकता है

Standard तरीकों की सीमाएँ

  • सबसे बड़ी problem यह है कि सभी techniques reliably उस issue से जुड़ी warning नहीं बनातीं जिसे solve करना है
  • todo!() अक्सर warning trigger करता है, लेकिन कई बार वह indirect causes से आती है, जैसे function argument use न होना या required mutation न होना
  • unwrap और clone बिल्कुल warning नहीं देते, इसलिए final PR से पहले दोबारा पढ़कर हटाने पड़ते हैं
  • unwrap legitimately production में रह भी सकता है, इसलिए temporary WIP use और normal use mix हो सकते हैं
  • /// documentation comment trick hack जैसी है, और expression position में documentation comments forbidden होने से compile error आ सकता है
  • अगर /// ऐसी जगह लिखा जाए जहाँ वास्तव में documentation बनती है, तो expected warning नहीं आती और deployment code में unwanted documentation बच सकती है

wip crate के tools

  • wip crate Rust development के दौरान “बाद में handle करने वाली warnings” strategy को ज़्यादा आसान बनाने के लिए tools का set है
  • इसे normal crate की तरह add किया जा सकता है
    • cargo add wip
  • ज़रूरी files में prelude import किया जा सकता है
    • use wip::prelude::*;
  • wip Rust की deprecation warning पर निर्भर करता है, इसलिए editor में deprecated function calls पर strikethrough दिखाने वाला feature बंद करना कम distracting हो सकता है
  • wip::wip!

    • wip! macro todo! की तरह काम करता है, लेकिन use करने पर हमेशा warning देता है
    • अधूरे code को mark करने के लिए जहाँ todo! इस्तेमाल करते थे, वहाँ इसे इस्तेमाल किया जा सकता है
    • शुरुआती version में इसका नाम todo था, लेकिन जब dependency std macro को shadow करने की कोशिश करती है तो Rust उसे prefer नहीं करता, जिससे prelude use में problem आई
  • unwrap_wip और clone_wip

    • unwrap_wip और clone_wip Result और Option के लिए extension trait के रूप में implement किए गए हैं
    • Scope में होने पर ये normal unwrap, clone की तरह काम करते हैं, लेकिन use करते समय warning देते हैं
    • यह साफ़ signal देता है कि final code में unwrap या clone छोड़ने का intent नहीं है
  • wip::fixme!

    • fixme! macro wip! का non-panicking variant है और expression की जगह नहीं लेता
    • इसे TODO comment की तरह इस्तेमाल किया जा सकता है, लेकिन /// trick के बिना reliably warning generate करता है
    • बाद में ठीक करने वाले notes छोड़ने के लिए इसे अक्सर इस्तेमाल किया जाता है

इस्तेमाल का flow और सावधानियाँ

  • wip crate हाल के कुछ PRs में इस्तेमाल हुआ है, और बड़े PRs में से एक Meilisearch PR #6484 में भी इस्तेमाल हुआ
  • Typical flow यह है कि development के दौरान workspace के crate में wip dependency add की जाती है, और review से पहले history rewrite करते समय उसे remove किया जाता है
  • wip happy path पर focus करते हुए बाद में handle करने वाले काम भूलने नहीं देता
  • इसके drawbacks भी हैं
    • fixme add करते समय पर्याप्त context न लिखा जाए तो बाद में ठीक करना मुश्किल हो सकता है
    • बड़े PR में wip से बनी warnings की संख्या बढ़कर burden बन सकती है
  • Current API docs.rs की wip documentation में देखी जा सकती है

1 टिप्पणियां

 
GN⁺ 5 시간 전
Lobste.rs की टिप्पणियां
  • ईमानदारी से कहूं तो wip के तर्क को देखकर ऐसा लगता है कि टूल्स को असल में कैसे इस्तेमाल किया जाना चाहिए, यह समझे बिना Clippy को फिर से बना दिया गया है
    टूलचेन डेवलपर्स यह अलग करते आए हैं कि क्या rustc warning बनेगी और क्या Clippy lint, लेकिन wip मौजूदा फीचर को नए नाम से दोबारा बनाकर cargo clippy में मौजूद lint को जबरन cargo check और cargo build की तरफ खिसकाता हुआ लगता है
    उदाहरण के लिए, wip! की व्याख्या नई dependency और non-standard syntax जोड़ने की कीमत पर #![warn(clippy::todo)] को cargo clippy के बाहर भी दिखाने की है, और unwrap_wip भी #![warn(clippy::unwrap_used)] जैसा है
    मेरे लिए .unwrap() prototyping के लिए है, इसलिए मैं #![warn(clippy::unwrap_used)] चालू करता हूं, और .expect("description of invariant") production के लिए है जहां .unwrap_wip() के बजाय .unwrap() इस्तेमाल किया जा सकता है, इसलिए मैं #![warn(clippy::expect_used)] चालू नहीं करता। जैसे: .expect("parse regex from const")
    clone_wip और fixme! के बारे में पक्का नहीं कह सकता, और ऐसे मामलों को developers से कैसे handle करवाने का इरादा था, इसका जवाब देने लायक मैंने सोचा भी नहीं है

    • यह बात भले ईमानदार हो, लेकिन बहुत सौम्य अभिव्यक्ति नहीं है :-) मैं इस क्षेत्र को 10 साल से इस्तेमाल कर रहा हूं, और जाहिर है इसका मतलब यह नहीं कि मुझे सब कुछ पता है, लेकिन सद्भावना से व्याख्या तो की जा सकती है
      हालांकि यह बात सही है कि लेख में Clippy जैसे मौजूदा linter पर चर्चा छूट गई है। यह मेरे निजी workflow से भी जुड़ा है: पहले मैं save करते समय Clippy चलाता था, लेकिन हमारे project में यह इतना धीमा था कि saves के बीच कई मिनट लग जाते थे, इसलिए मैंने छोड़ दिया; अब इसे सिर्फ CI और push से पहले वाले hook में चलाता हूं
      warnings को rustc में लाने से यह काफी तेज हो जाता है, और iterative काम के दौरान inline warnings दिखती हैं, इसलिए यह इस workflow का मुख्य हिस्सा बन जाता है
      Clippy configuration भी, कम से कम पिछली बार जब मैंने देखा था, काफी झंझटभरी थी। workspace के सभी crates में lints को code से एक साथ specify करने का तरीका नहीं था, इसलिए workspace में जिन lints को deny करना हो, उनके लिए CI में explicit -D जोड़ना पड़ता था या सभी crates में global attributes लगातार डालने पड़ते थे
      #![warn(clippy::todo)] सभी crates में चाहिए, इसलिए development के दौरान ही crate जोड़ने/हटाने की तुलना में यह हमारे लिए कोई बड़ा फायदा नहीं है। मुझे लगता है कि todo! पर default रूप से rustc या Clippy में warning आनी चाहिए, और मैं मानता हूं कि ऐसा होने पर wip की उपयोगिता घट जाएगी
      unwrap_wip को #![warn(clippy::unwrap_used)] कहना मुझे दो वजहों से सही नहीं लगता। पहला, इरादे के लिहाज से unwrap non-production और expect production है—यह फर्क आम है, लेकिन universal नहीं। दूसरा, मौजूदा codebase में करीब 4600 unwrap हैं, और सभी को expect में बदला जा सकता है, लेकिन तब जगह-जगह temporary messages डालने या “यह mutex poison नहीं हुआ है”, “इस operation की failure को caller तक propagate करना चाहता हूं” जैसी preconditions को फिर से गढ़ने में बहुत ज्यादा समय लग जाएगा
      fixme! बहुत उपयोगी है, और अच्छा होगा अगर यह standard library आदि में आ जाए। असल में इस crate में मैं सबसे ज्यादा यही feature इस्तेमाल करता हूं
      लेख में ये details नहीं आईं, लेकिन यह crate standard नहीं बल्कि specialized library है, इसलिए इसमें wip_iter जैसे feature भी हैं। यह iterator के return-position impl Trait functions में todo जैसे macros इस्तेमाल करने देता है, और wip_future भी ऐसा ही है। return type inference और todo साथ में काम न करने के कारण जबरन type बनाने पड़े, ऐसा मेरे साथ काफी बार हुआ है। किसी ने release mode में default रूप से compile error देने का सुझाव भी दिया है, और इसे दूसरे improvement ideas के साथ देखूंगा
      नई dependency और non-standard syntax की लागत को थोड़ा संतुलित तरीके से देखें तो यह development के दौरान ही इस्तेमाल होने वाली single-file dependency है, जिसे अंत में आम तौर पर हटा दिया जाता है
  • unwrap के साथ prototyping करते समय समस्या यह है कि बाद में errors को ठीक से introduce करना हो तो return type का shape बदल सकता है, और खासकर अगर वे functions functional style में इस्तेमाल हों तो cascading effect पैदा होता है
    error type के लिए Box<dyn Error>, string, anyhow आदि इस्तेमाल करके Result से शुरू करने की सलाह दूंगा। बाद में इसे असली error enum में बदलना संभव है, लेकिन जो code failure-free दिखता हो उसे बाद में Result return करने के लिए refactor करना ज्यादा मुश्किल होता है
    साथ ही अगर LLM इस्तेमाल कर रहे हैं तो सावधान रहें। LLM pattern-matching machines हैं, इसलिए वे “जानबूझकर अभी rough रखा है और बाद में ठीक करना है” और “इस project में errors ऐसे handle किए जाने चाहिए” में फर्क नहीं कर पाते। समय के साथ वे “बस अभी के लिए” वाले फैसलों को बढ़ा सकते हैं, इसलिए main में एक-दो comments से यह guide करना मददगार होगा कि ढीली error handling कहां/कब ठीक है

  • todo!() और unimplemented!() की व्याख्या पर मैं और documentation सहमत नहीं हैं। हालांकि छोड़ा गया अगला paragraph उस रुख को कुछ हद तक संतुलित करता है
    documentation में लिखा है कि todo! बाद में implement करने के इरादे को बताता है और message “not yet implemented” है, जबकि unimplemented! ऐसा वादा नहीं करता और उसका message “not implemented” है
    जब todo! 1.40.0 में जोड़ा गया था, तब यह सिर्फ एक छोटा नाम था, और नाम के अलावा unimplemented! से कोई फर्क नहीं था
    1.42.0 में जाकर unimplemented message से “yet” हटाया गया। जहां तक याद है, दोनों macros के इन meanings को तय करना थोड़ा विवादास्पद था, और todo! को unimplemented! के short form के रूप में ship करना भी वैसा ही था
    आजकल unimplemented!() उन जगहों के लिए recommend किया जाता है जैसे trait implementations, जहां technical वजहों से method body देनी पड़ती है लेकिन उसे कभी call नहीं किया जाना चाहिए। documentation का example भी ठीक ऐसा ही है

  • development के दौरान workspace के crate में wip dependency जोड़ना और review से पहले history rewrite करके हटाना—क्या यह flow extra tooling से और smooth बनाया जा सकता है, यह सोच रहा हूं
    PR के बीच में changes edit करते हुए lock file rewrite तक करना थोड़ा खटकता है। अगर उस दौरान dependency tree कहीं और भी बदला हो तो unnecessary conflicts हो सकते हैं। शायद jj fix का दुरुपयोग किया जा सकता है। खैर, project idea बहुत अच्छा है, और लगता है कहीं न कहीं इस्तेमाल करूंगा

  • wip::fixme! जैसा panic न करने वाला note-taking macro सच में अच्छा idea है। doc comment trick भी याद रखने लायक neat तरीका है

  • कुछ interesting ideas हैं जो rustc में बदलाव तक ले जाएं तो अच्छा होगा। todo!() पर सचमुच default warning आनी चाहिए
    जब developer hacking mode में हो तो ऐसी warnings बंद कर सके, यह और अच्छा होगा। बहुत ज्यादा warnings भारी लगती हैं, और आम तौर पर IDE के नीचे 19-line console में cargo चलाया जाता है। development के दौरान warnings असली errors और ज्यादा अहम warnings को दबा देती हैं। अगर हमेशा errors को अंत में sort करने का अनुशासन ही हो, तो भी यह समस्या कम हो सकती है
    साथ ही .unwrap_wip() और .clone_wip() नाम original नामों से लंबे हैं, इसलिए बहुत लंबे लगते हैं। “dev unwrap” और “dev clone” के abbreviations के रूप में .du() और .dc() जैसे नाम सुझाऊंगा। quick programming में ये ज्यादा आसान होंगे

  • क्या जरूरत वाली जगहों पर .clone() और .unwrap() को automatically add नहीं किया जा सकता? जैसे #[wip(autoclone,autounwrap)] जैसा कोई flag या feature। शायद संभव नहीं होगा, लेकिन सपने तो देख सकते हैं

  • जब भी नया project या prototype शुरू करता हूं, main.rs में ये lines डालता हूं:
    #![allow(dead_code)]
    #![allow(unused_variables)]
    #![allow(unreachable_code)]
    शुरुआती stage में बहुत सारा शोर कम हो जाता है