Rust में WIP कोड को warning के रूप में छोड़ना
(blog.dureuill.net)- 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औरclonewarning नहीं देते,todo!भी indirect warnings पर निर्भर करता है, और///trick location के आधार पर compile error या अनचाही documentation में बदल सकती है wipcratewip!,unwrap_wip,clone_wip,fixme!के ज़रिए temporary implementation, temporaryunwrap/clone, और बाद में ठीक करने वाले notes को हमेशा warnings के रूप में दिखाता है- Meilisearch CI में
-D warningसे warnings को errors में बदलकर WIP code को शामिल होने से रोकता है, और development के दौरान जोड़ी गईwipdependency को review से पहले history cleanup के दौरान हटाने का flow इस्तेमाल करता है
Rust development के दौरान warnings की ज़रूरत क्यों पड़ती है
- Rust correctness को अहमियत देने वाली language है, लेकिन development के दौरान हर error path को तुरंत handle करने से implementation speed और focus कम हो सकता है
- जब
rustcResultreturn, 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 warningflag के साथ चलाता है, जिससे सभी 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 है
- Rust standard library का
-
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 किया जा सकता है
- Rust compiler की default warnings भी development के दौरान अधूरी state दिखाने में उपयोगी हैं
Standard तरीकों की सीमाएँ
- सबसे बड़ी problem यह है कि सभी techniques reliably उस issue से जुड़ी warning नहीं बनातीं जिसे solve करना है
todo!()अक्सर warning trigger करता है, लेकिन कई बार वह indirect causes से आती है, जैसे function argument use न होना या required mutation न होनाunwrapऔरcloneबिल्कुल warning नहीं देते, इसलिए final PR से पहले दोबारा पढ़कर हटाने पड़ते हैंunwraplegitimately 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
wipcrate Rust development के दौरान “बाद में handle करने वाली warnings” strategy को ज़्यादा आसान बनाने के लिए tools का set है- इसे normal crate की तरह add किया जा सकता है
cargo add wip
- ज़रूरी files में prelude import किया जा सकता है
use wip::prelude::*;
wipRust की deprecation warning पर निर्भर करता है, इसलिए editor में deprecated function calls पर strikethrough दिखाने वाला feature बंद करना कम distracting हो सकता है-
wip::wip!wip!macrotodo!की तरह काम करता है, लेकिन use करने पर हमेशा warning देता है- अधूरे code को mark करने के लिए जहाँ
todo!इस्तेमाल करते थे, वहाँ इसे इस्तेमाल किया जा सकता है - शुरुआती version में इसका नाम
todoथा, लेकिन जब dependencystdmacro को shadow करने की कोशिश करती है तो Rust उसे prefer नहीं करता, जिससे prelude use में problem आई
-
unwrap_wipऔरclone_wipunwrap_wipऔरclone_wipResultऔरOptionके लिए extension trait के रूप में implement किए गए हैं- Scope में होने पर ये normal
unwrap,cloneकी तरह काम करते हैं, लेकिन use करते समय warning देते हैं - यह साफ़ signal देता है कि final code में
unwrapयाcloneछोड़ने का intent नहीं है
-
wip::fixme!fixme!macrowip!का non-panicking variant है और expression की जगह नहीं लेता- इसे TODO comment की तरह इस्तेमाल किया जा सकता है, लेकिन
///trick के बिना reliably warning generate करता है - बाद में ठीक करने वाले notes छोड़ने के लिए इसे अक्सर इस्तेमाल किया जाता है
इस्तेमाल का flow और सावधानियाँ
wipcrate हाल के कुछ PRs में इस्तेमाल हुआ है, और बड़े PRs में से एक Meilisearch PR #6484 में भी इस्तेमाल हुआ- Typical flow यह है कि development के दौरान workspace के crate में
wipdependency add की जाती है, और review से पहले history rewrite करते समय उसे remove किया जाता है wiphappy path पर focus करते हुए बाद में handle करने वाले काम भूलने नहीं देता- इसके drawbacks भी हैं
fixmeadd करते समय पर्याप्त context न लिखा जाए तो बाद में ठीक करना मुश्किल हो सकता है- बड़े PR में
wipसे बनी warnings की संख्या बढ़कर burden बन सकती है
- Current API docs.rs की
wipdocumentation में देखी जा सकती है
1 टिप्पणियां
Lobste.rs की टिप्पणियां
ईमानदारी से कहूं तो
wipके तर्क को देखकर ऐसा लगता है कि टूल्स को असल में कैसे इस्तेमाल किया जाना चाहिए, यह समझे बिना Clippy को फिर से बना दिया गया हैटूलचेन डेवलपर्स यह अलग करते आए हैं कि क्या
rustcwarning बनेगी और क्या 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 करवाने का इरादा था, इसका जवाब देने लायक मैंने सोचा भी नहीं हैहालांकि यह बात सही है कि लेख में 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)]कहना मुझे दो वजहों से सही नहीं लगता। पहला, इरादे के लिहाज सेunwrapnon-production औरexpectproduction है—यह फर्क आम है, लेकिन 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-positionimpl Traitfunctions में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 दिखता हो उसे बाद मेंResultreturn करने के लिए 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 में जाकर
unimplementedmessage से “yet” हटाया गया। जहां तक याद है, दोनों macros के इन meanings को तय करना थोड़ा विवादास्पद था, औरtodo!कोunimplemented!के short form के रूप में ship करना भी वैसा ही थाआजकल
unimplemented!()उन जगहों के लिए recommend किया जाता है जैसे trait implementations, जहां technical वजहों से method body देनी पड़ती है लेकिन उसे कभी call नहीं किया जाना चाहिए। documentation का example भी ठीक ऐसा ही हैdevelopment के दौरान workspace के crate में
wipdependency जोड़ना और 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 में बहुत सारा शोर कम हो जाता है