- Rust का
Pinasync/await द्वारा बनाए गए Future के अंदर self-referential state को सुरक्षित तरीके से संभालने के लिए पेश किया गया एक बुनियादी तत्व है - async Future हर await point पर state सहेजता है, इसलिए किसी object के अंदर एक field उसी object के दूसरे field को reference कर सकता है और वह self-referential type बन सकता है
- move constructor, offset pointer,
?Moveडिज़ाइन क्रमशः runtime tracking cost, compile-time feasibility, और मौजूदा API के साथ backward compatibility समस्याओं के कारण अपनाए नहीं जा सके - अंतिम डिज़ाइन
Pinहै, जो pointer को wrap करके target को pinned typestate में डालता है;Unpinauto trait की वजह से ज़्यादातर types पहले की तरह move हो सकते हैं Pinकी कठिनाई immutability की अवधारणा से ज़्यादा library type की सीमाओं से आती है; reborrowing,Pin::set, pinned projection, औरDropके साथ interaction इसकी usability को काफी घटाते हैं
Pin की ज़रूरत वाली समस्या
- Rust async ecosystem में
Pinऔर pinning core foundation हैं, लेकिन async Rust सीखने वालों के लिए यह अब भी कठिन और गलतफहमियों से भरा क्षेत्र है Pinका उद्देश्य users को केवल safe Rust से खुद self-referential types बनाने देना नहीं है- इसका मकसद compiler द्वारा async functions से बनाए गए self-referential Future या tokio जैसे runtimes द्वारा unsafe code से बनाए गए self-referential types को सुरक्षित तरीके से manipulate करने देना है
- उदाहरण में
async fn bar,foo(&mut z).awaitpoint परzऔरzको reference करने वालेFooFuture को उसी Future state में साथ-साथ store करना पड़ता है- उस समय Future object के अंदर एक field उसी object के दूसरे field को reference करता है
- ऐसा Future type self-referential type बन जाता है
- object ऐसी state में जाने के बाद move हो जाए, तो internal reference पुरानी memory location की ओर point करता है, और वह location dead memory हो सकती है या किसी दूसरी value के लिए reused हो सकती है
Pinसे पहले Rust में ownership होने पर या mutable reference होने पर object को move किया जा सकता था, इसलिए किसी खास point के बाद move रोकने को express करने का तरीका चाहिए था
जो approaches समाधान नहीं बन सके
-
move constructor
- move constructor वह तरीका है जिसमें value move होने पर destructor की तरह code चलता है और self-referential pointers को नई location के हिसाब से ठीक करता है
- Rust में pointers सिर्फ move होने वाली value के “अंदर” ही नहीं होते; उदाहरण के लिए, वे अपनी state की ओर point करने वाले pointers के vector में भी हो सकते हैं
- ऐसे सभी pointers को track करने के लिए अंततः garbage collection जैसी runtime memory management चाहिए होगी
- Rust ने शुरुआती चरण में move constructor न रखने का फैसला किया था, और बहुत-सा unsafe code इस assumption पर निर्भर करता है कि values को सिर्फ memory copy से move किया जा सकता है
- move constructor को बाद में जोड़ना breaking change बन जाता
-
offset pointer
- offset pointer वह तरीका है जिसमें self-reference को normal reference के बजाय self-referential object address के base पर offset के रूप में compile किया जाता है
- compile time पर हमेशा यह तय नहीं किया जा सकता कि कौन-सा reference self-reference है
- अलग-अलग branches में वही value कभी अपने object के अंदर point कर सकती है और कभी बाहर
- इसे संभालने के लिए references को offset और reference के enum जैसे रूप में compile करना पड़ेगा; async/await पर काम करते समय इसे अव्यावहारिक माना गया
pinned typestate की requirements
- self-referential Future को शुरुआत से हमेशा immovable होना ज़रूरी नहीं; वह lifecycle के किसी point तक स्वतंत्र रूप से move हो सकता है, लेकिन किसी खास point के बाद move नहीं होना चाहिए
- Future को दूसरे Future के साथ compose करते समय उसे movable होना चाहिए
- poll होने के दौरान जहां वह live रहेगा, वहां place हो जाने के बाद उसे आगे move नहीं होना चाहिए
- Ralf Jung का model मौजूदा “owned” और “shared” typestate के बाद self-referential Future के लिए तीसरी state, pinned typestate, जोड़ता है
- object pinned typestate में चला जाए तो उसे फिर कभी move नहीं करना चाहिए
- और अधिक सटीक रूप से, पहले destructor चलाए बिना उस object की memory को invalidate नहीं करना चाहिए
- व्यावहारिक रूप से इसे object को नई location पर न ले जाने की requirement माना जा सकता है
- ज़्यादातर types self-reference शामिल नहीं कर सकते, इसलिए pinned typestate का उनके लिए खास मतलब नहीं होता
- ऐसे types को pinning की constraints से बाहर आकर फिर से move हो सकना बेहतर है
- pinned typestate का detailed formal model Ralf Jung के A Formal Look at Pinning में दिया गया है
?Move डिज़ाइन क्यों विफल हुआ
Pinसे पहलेMoveनाम के नए trait पर आधारित डिज़ाइन आज़माया गया था- ज़्यादातर types
Moveimplement करते - जो types self-reference शामिल कर सकते थे, वे
Moveimplement नहीं करते Moveimplement न करने वाले type की value का reference बनाते ही वह value pinned typestate में चली जाती और फिर move नहीं हो सकती
- ज़्यादातर types
- यह तरीका intuitive था, क्योंकि reference बनाने के point और pinning transition को जोड़कर safety guarantee की जाती थी
- वास्तव में इसे compiler branch में implement भी किया गया था
- मूल सीमा यह थी कि कभी-कभी ऐसी value को थोड़ी देर reference करना होता है जो बाद में self-referential बनेगी, लेकिन अभी pin नहीं करना होता
- उदाहरण के लिए, value को थोड़ी देर
Optionमें store करके फिरOption::takeसे निकालना चाह सकते हैं
- उदाहरण के लिए, value को थोड़ी देर
- इससे बड़ी समस्या backward compatibility थी
Moveको auto trait नहीं बनाया जा सकता था- क्योंकि
mem::swapजैसी stable APIs पहले से मौजूद थीं, जो mutable reference से value को हमेशा move कर सकने पर निर्भर करती हैं
?Moveके रूप में जोड़ने का तरीका भी associated type के कारण backward compatible नहीं था- trait के associated type में
?Traitbound जोड़ने की जगह trait definition होती है - मौजूदा trait के associated type bound को relax करने से उस bound पर निर्भर code टूट सकता है
IntoFutureका associated future type,DerefMutकाTarget, function return types, iterator item, index operator return value, arithmetic operator return value आदि कई basic operations associated type से जुड़े हैं
- trait के associated type में
- edition से भी इसे आसानी से हल नहीं किया जा सकता था
- क्योंकि अलग-अलग editions के crates को साथ compose करने के लिए trait interface समान रहना ज़रूरी है
Pin डिज़ाइन
- अंतिम डिज़ाइन pinned typestate को object type की property नहीं, बल्कि special pointer द्वारा बनाई गई state के रूप में express करता है
Pinpointer को wrap करने वाला wrapper type है- built-in reference type को भी wrap कर सकता है
Boxजैसे library-defined smart pointer को भी wrap कर सकता है
Pinउस pointer के target को pinned typestate में डालता है, और target को फिर move नहीं करना चाहिए- बदलावों को कम रखने के लिए यह डिज़ाइन compiler feature नहीं, बल्कि library API के रूप में implement किया गया है
- pinned object को वास्तव में modify करने वाला code unsafe API से access करना पड़ता है
- इस समय यह guarantee देनी पड़ती है कि normal mutable reference के जरिए object move नहीं होगा
- ज़्यादातर types में pinned state और normal state के बीच कोई meaningful difference नहीं होता, इसलिए
Unpinauto trait जोड़ा गया- अगर type self-referential नहीं हो सकता, तो pinned pointer से unsafe के बिना mutable reference पाया जा सकता है
Unpinimplement करने वाले object कोPinसे बाहर move करना safe है
- pinning केवल pinned pointer पर लागू होती है, इसलिए normal unpinned reference
Unpinन होने वाले types के साथ भी काम करते रहते हैं - अतिरिक्त व्याख्या standard docs के
Pintype औरpinmodule में दी गई है - इस डिज़ाइन का सबसे बड़ा फायदा यह था कि इसे existing code तोड़े बिना जोड़ा जा सकता था
swapजैसी APIs, जो referenced data को move कर सकती हैं, mutable reference मांगती हैं- object को
Pinसे pin करने पर ऐसी APIs उस object पर आगे call नहीं की जा सकतीं - pinned typestate सिर्फ special pinned reference पर लागू होती है, इसलिए Rust language की overall backward compatibility guarantee नहीं टूटती
Pin की usability समस्याएं
Pinने requirements को backward-compatible तरीके से पूरा किया, लेकिन जैसे ही user इसे सीधे handle करता है, complexity cliff पैदा हो जाती है- एक explanation यह है कि pinned object को modify करने के लिए unsafe code चाहिए
- हालांकि इस समस्या को बढ़ा-चढ़ाकर नहीं देखना चाहिए
Pin::setसे pinned object में safely assignment किया जा सकता है- वास्तव में pinned object को modify करने वाला code आमतौर पर compiler-generated code होता है, जो async function को Future में lower करता है; users द्वारा सीधे लिखे जाने के मामले कम हैं
- यह explanation भी core reason नहीं है कि
Pinइसलिए कठिन है क्योंकि यह conditional तरीके से behave करता है- Rust में ऐसे features हैं जो conditions के आधार पर अलग behavior रखते हुए भी समझना आसान बनाते हैं
- non-lexical lifetimes इसका उदाहरण हैं, जहां conditional branches के हिसाब से lifetime अलग-अलग points पर खत्म हो सकती है
- core problem यह है कि
Pinएक pure library type है, जबकि normal reference type language built-in type है जिसे कई syntax supports और sugar मिलते हैं- normal references में स्वाभाविक रूप से मिलने वाली capabilities pinned reference में गायब हो जाती हैं
- compiler द्वारा स्वीकार किए जाने वाले reference behavior के आधार पर user ने जो mental model बनाया होता है, वह pinned reference में टूट जाता है
reborrowing और Pin::as_mut
- normal mutable reference
&mut T,Copyimplement नहीं करता, फिर भी उसे same argument के रूप में कई बार pass किया जा सकता है- क्योंकि compiler implicit रूप से reborrowing करता है, जैसे
xकी जगह&mut *xडाल दिया गया हो
- क्योंकि compiler implicit रूप से reborrowing करता है, जैसे
Pin<&mut T>normal library type है औरCopyimplement नहीं करता, इसलिए यह सुविधा नहीं मिलतीPin<&mut T>को दो या अधिक बार use करने पर move के बाद value use करने की error आ सकती है, या समझने में और कठिन lifetime error आ सकती है- reborrow करने के लिए explicit रूप से
Pin::as_mutcall करना पड़ता है
- normal mutable reference में dereference और assignment operator से सीधे assign किया जा सकता है, लेकिन
Pinमेंsetmethod सीखना पड़ता है- ऐसी special APIs बढ़ने की वजह यह है कि
Pinlanguage syntax support के बिना library type है
- ऐसी special APIs बढ़ने की वजह यह है कि
pinned projection और Drop
- pinned projection का मतलब है object के pinned reference से उस object के field का pinned reference पाने की समस्या
- projection का अर्थ object से field तक access करना है
- यह normal reference field access से कहीं कठिन है, इसलिए pin-project-lite जैसे third-party crate का उपयोग होता है
- ऐसे crates में macro सहित complex नई APIs सीखनी पड़ती हैं
- सबसे खराब interaction pinned projection और
Droptrait के बीच होता हैDrop::dropnormal mutable reference लेता है- अगर किसी type में self-referential field हो, और उस field पर pin project करके poll करने के बाद destructor में उसी field को move कर दिया जाए, तो pinning guarantee टूट सकती है
- उदाहरण के लिए, destructor के अंदर उस future को stack पर pin करके poll करने से existing pinning guarantee violate हो जाती है
pin-project-liteजैसे crates destructor define करने की क्षमता को restrict करके इस समस्या को handle करते हैं- practical तौर पर यह काम करता है, लेकिन pinning guarantee समझाते समय documentation में complexity जोड़ता है
Drop,Pinसे पहले stable था, इसलिए workaround की जरूरत थी
मौजूदा मूल्यांकन और अगले सुधार की दिशा
Pinने arbitrary references शामिल करने वाले async functions को safe self-referential objects में compile करना संभव बनाया- references Rust users के code लिखने के basic तरीके का अहम हिस्सा हैं, इसलिए इसके बिना async/await की usability काफी कम हो जाती
- साथ ही
Pinको existing Rust के साथ पूरी तरह backward-compatible तरीके से जोड़ा गया Pinhigh-performance network services और asynchronous programming के दूसरे use cases को support करने वाले ecosystem का core building block बन गया है- लेकिन pinned reference को handle करना ordinary reference संभालने से कहीं ज्यादा कठिन है, और
Pinसच में complexity cliff बनाता है - अगले सुधार की दिशा का core concept pinned places है
1 टिप्पणियां
Hacker News की राय
मुझे हमेशा लगता रहा है कि Pin को आधिकारिक docs में साफ़ तरीके से नहीं समझाया गया है, इसलिए इसे समझना मुश्किल है
खासकर “Pin यह गारंटी देता है कि object कभी move नहीं होगा” जैसी व्याख्याएँ बहुत मिलती हैं, लेकिन यह सच नहीं है
यह बात सिर्फ़ तब सही है जब object
Unpinन हो; ज़्यादातर सामान्य objectsUnpinहोते हैं, इसलिए Pin आम तौर पर कुछ नहीं करतायह समझने में मुझे बहुत समय लगा, और जिन types
Tके लिए Pin का सच में मतलब होता है, उनका set काफ़ी खास और अजीब है; मुझे लगता है docs इस बात पर पर्याप्त ज़ोर नहीं देतेबेशक जिन types को आप वास्तव में pinned state में handle करेंगे—futures और streams—उनके ऐसे खास objects होने की संभावना कहीं ज़्यादा है
फिर भी मुझे लगता है कि पिछले कुछ वर्षों में docs बहुत बेहतर हुए हैं
यह लेख लिखते समय जब मैंने देखा, तो हैरानी हुई कि वे काफ़ी सही points पर focus कर रहे थे; मुझे याद है कि 2019 के आसपास वे
stdAPI docs की बजाय Rust reference docs में जाने लायक contract specification की तरफ़ कहीं ज़्यादा झुके हुए थेमुझे लगता है users को Pin मुश्किल लगने की वजह यह है कि Pin अपने-आप में कोई meaning नहीं रखता
यह language के दूसरे wrappers से अलग है; अगर कोई exception हो तो वह
AssertUnwindSafeजैसा है, जिसे उसके मूल उद्देश्य के लिए लगभग कोई इस्तेमाल नहीं करताजब आपके पास
Pin<&mut InnerType>हो, तो language या standard library में मौजूद Pin अकेले यह नहीं बताता कि आप क्या कर सकते हैं और क्या नहींबस अगर
InnerTypeकोUnpinघोषित किया गया है, तो इसका मतलब है कि आप वह सब कर सकते हैं जो एक सामान्य pointer से कर सकते हैंइसके बजाय Pin “meaning खुद लेकर आओ” वाले तरीके से काम करता है, और
InnerTypeprovider pinned object को safely manipulate करने के लिए internallyunsafemethods और APIs अलग से बनाता हैPin का अपना उद्देश्य ऐसे pointer देना है जिनमें
&mutreplacement,Boxसे निकालकर move करना जैसी intrinsic capabilities कम हों, ताकि inner type उनके ऊपर extra capabilities को safely allow कर सकेमुझे लगता है इसी meaning की अस्पष्टता लोगों को सबसे ज़्यादा confuse करती है, और मुझे भी इसे समझने में काफ़ी समय लगा
structural fields और non-structural fields की concept बस “यह field सामान्य data है, लेकिन वह field अपने-आप pinned रहना चाहने वाले object को रखता है” जैसे common access patterns को संभव बनाने की व्यवस्था है
Unpinimplement नहीं करता, इस pointer का target फिर कभी move नहीं किया जा सकताऔर ज़्यादा सटीक कहें तो इसका मतलब है कि destructor चलाए बिना target को invalidate नहीं किया जा सकता, और move के problematic होने की वजह भी यही है
कुछ अधिकार छोड़ने पर self-referential values store करने के अधिकार जैसे दूसरे अधिकार मिलते हैं
components के बीच contracts आम तौर पर इसी तरह काम करते हैं
इसी तरह reference के ज़रिए mutate करने का अधिकार छोड़ने पर आप उसी reference को alias भी बना सकते हैं
जब भी इस बात के बारे में सोचता हूँ, तो हालांकि यह बिल्कुल अलग और कहीं भारी विषय है, मुझे फिल्म Lincoln की line याद आती है: “अगर हम कानून का पालन करें, Alex, यहाँ तक कि स्वतंत्रता खोने तक पालन करें — जैसे दमन करने की स्वतंत्रता — तो शायद हम ऐसी दूसरी स्वतंत्रताएँ खोज लें जिन्हें पहले नहीं जानते थे”
लेकिन मैं सहमत हूँ कि safe code में ऐसे अधिकारों को सीधे इस्तेमाल न कर पाना educational रूप से problem है
क्योंकि pinned reference से क्या किया जा सकता है, इसे “compiler ने जो
pollmethod बनाया है उसे call करो” के अलावा आसानी से दिखाना मुश्किल हैमैंने कई सालों तक Rust में professionally develop किया है, लेकिन सच कहूँ तो Pin को इतना अच्छी तरह नहीं समझता
theory पता है, लेकिन कब इस्तेमाल करना है इसकी intuition ज़्यादा नहीं है
Pin का इस्तेमाल असल में कुछ ऐसा है: “कुछ try किया तो compiler ने शिकायत की, फिर यह-वह pin किया और compile हो गया”
रोज़मर्रा की coding में यह अब तक ऐसा obstacle नहीं बना कि सच में बैठकर गहराई से समझना पड़े
unsafeसे बचो, और smart compiler लोगों ने पहले ही सब solve कर दिया है इसके लिए शुक्रगुज़ार रहो” जैसी होती हैइसके उलट C++ में मैं अक्सर “वे चीज़ें जिन्हें नहीं जानता लेकिन इस्तेमाल करना ही पड़ता है” वाले किनारे पर चलता था और मगरमच्छों का शिकार हो जाता था
पढ़ाते समय, अगर यह साफ़ करना हो कि
Unpinitems परPinका असर नहीं पड़ता, तो उन real-world analogies का इस्तेमाल करना अच्छा होगा जहाँ चीज़ों को जगह पर पकड़ने के लिए बना tool भी असर नहीं करताVelcro hooks चिकनी सतह पर नहीं चिपकते:
Pin→ Velcro,Unpin→ चिकनी सतहmagnet non-magnetic materials पर असर नहीं करता:
Pin→ magnet,Unpin→ non-magnetic/glass/brassglue non-stick surface पर नहीं चिपकता:
Pin→ glue,Unpin→ non-stickइससे यह साफ़ हो जाता है कि “Velcro” चीज़ को जगह पर fix करता है, लेकिन अगर चीज़ “चिकनी” है तो Velcro mechanism का उस पर असर नहीं होता
Rust ecosystem में naming के माहौल को देखते हुए, अगर trait names को magnet और non-magnetic जैसी दिशा में बदला गया होता तो सुंदर होता
मुझे लगता है
Unpinका मतलब है कि object किसी भी समय pinned होने के लिए तैयार हैकल रात article पढ़ा था, लेकिन यह पहले ही भूल गया हूँ कि pinning के लिए कोई correction step चाहिए या नहीं
इसलिए
T: Pin + !Unpinमेरे हिसाब से ऐसे कागज़ जैसा है जिसे सिर्फ़ staple से fix किया जा सकता है, औरT: Pin + Unpinहुक लगी तस्वीर जैसा है जिसे कील पर टांगा जा सकता है और फिर हुक को नुकसान पहुँचाए बिना वापस उतारा जा सकता है“value identity” शब्द इस लेख में कहीं भी परिभाषित नहीं है और Mojo docs में भी मुझे नहीं मिला, इसलिए यह साफ़ नहीं है कि Modular किस आधार पर कहता है कि Mojo उस समस्या को हल करता है जिसे Pin हल करना चाहता है
मैं भी यह दावा नहीं कर रहा कि मुझे जवाब पता है, लेकिन Dave Abrahams की एक शानदार प्रस्तुति याद आती है, जिन्होंने Chris Lattner के साथ Swift के value semantics पर काम किया था
प्रस्तुति का शीर्षक “Value Semantics: Safety, Independence, Projection, & Future of Programming” है
[0] https://www.youtube.com/watch?v=QthAU-t3PQ4
Rust में references भी first-class types हैं, जबकि Swift और मेरी नज़र में Mojo references को सिर्फ़ parameter passing mechanism के रूप में अनुमति देते हैं
लगता है Mojo ने Swift के
inoutparameters को extend करके immutable reference passing mechanism भी जोड़ा हैअगर objects के अंदर references store करने की अनुमति न हो, तो Rust जिस तरह का code compile करता है वैसा implement नहीं किया जा सकता, इसलिए इससे “self-referential struct” समस्या तो हल हो जाती है
लेकिन उद्धृत paragraph Mojo के बारे में बिल्कुल यह बात नहीं कह रहा, इसलिए इसका मतलब क्या है यह काफ़ी confusing है
मेरी नज़र में समस्या यह है कि अगर किसी value का
&mutreference आपके पास है, तोmem::swap/replaceजैसी चीज़ों से उस value को move किया जा सकता हैलेकिन असल में ऐसा करना बहुत कम ज़रूरी होता है
अगर इसकी अनुमति न होती, तो self-referential values के
&mutreference लेना पूरी तरह safe होता लगता हैशायद ज़रूरत पड़ने पर reference के ज़रिए move को explicitly opt-in करने का कोई तरीका हो सकता था, और अगर
swapऔरreplaceकोunsafeबना दिया जाता तो शायद इस पूरी समस्या से बचा जा सकता थाकाश कोई इस design space को explore करे
&mutबहुत ज़्यादा शक्तिशाली हैअगर
&mutअपने अंदर की value को move करने का अधिकार न देता, तो पूरा design कहीं ज़्यादा सरल होताअगले लेख में मैं इस बारे में बात करूँगा
Rust को backward compatibility बनाए रखनी है और यह पहले ही तय कर चुका है कि
&mutसे values move की जा सकती हैं, लेकिन अगर पुराने फैसलों से बंधे न हों, तो निश्चित रूप से बहुत साफ़ design संभव हैmem::swapmutable reference के ज़रिए value move करने का बस एक तरीका है, और भी बहुत सारे तरीके हैंOption::takeएक example है जिसे मैं काफ़ी बार use करता हूँ, और अगर यहunsafeहोता तो वाकई अजीब होतायह background story देखकर अच्छा लगा। WithoutBoats पहले से ही बहुत timely topics—async iterators,
poll,pin—पर काफ़ी active discussions कर चुका हैhttps://news.ycombinator.com/from?site=without.boats
कम ही communities होंगी जो किसी language की बारीक internals में इतनी गहराई से publicly उतरती हैं, और इसे देखना काफ़ी मज़ेदार है
async अभी भी आधा-अधूरा है और बहुत complex है
यह मैं पिछले 3 साल से हफ़्ते में 40 घंटे Rust code लिखने के अनुभव से कह रहा हूँ
आप Rust जैसी किसी भाषा की कल्पना कर सकते हैं जिसमें move constructor हों, बनने वाले सभी
Futuresubtypes opaque हों और अपने-आप heap पर allocate हो जाते होंतब user के पास उसे destroy करने का कोई तरीका नहीं होगा, वह opaque होगा और heap में किसी दूसरी जगह होगा, इसलिए उसे move करने का भी तरीका नहीं होगा, और Pin की जरूरत खत्म हो सकती है
move constructor होने का मतलब है कि move को conceptually destroy करने के बाद फिर से create करना माना जाता है
इसका अच्छा असर यह है कि execution से पहले
Futureों को merge और inline किया जा सकता हैयह Rust की immutability से भी मिलता-जुलता है। immutable memory जैसी कोई चीज नहीं, सिर्फ immutable reference होते हैं
लेकिन तब हर async function call पर अलग allocation होगा, और यह memory locality के लिए बहुत खराब है
किसी तरह का virtual stack इससे कहीं बेहतर होगा, लेकिन stack को by default छोटा optimize करना हो तो आखिरकार garbage collection की जरूरत पड़ेगी
stdमेंCopyजैसी level पर language में built-inMovetrait जोड़ताMoveऐसा function define करे जो value को memory के एक address से दूसरे address पर move करे, और जिन structs मेंimpl Moveन हो उन्हें move न होने देलगभग हर type पर
#[derive(Move)]लगाया जाएगा, और इसके लिए bytes copy करने वाला simple move function implement करना काफी होगालेकिन ऐसा करने से self-referential types, futures, और ज्यादा complex move behavior की जरूरत रखने वाली कई चीजों के लिए रास्ता खुल जाएगा
असल में
CopyऔरCloneके फर्क को reflect करते हुए इसे दो traits में बांटना शायद ज्यादा समझदारी होगीएक marker trait जो compiler को बताए कि bytes को बस उठा कर ले जाना ठीक है, और दूसरा custom “move constructor” implementation की अनुमति देने वाला
Pin समझना बहुत मुश्किल है, इसलिए काश Move होता
एक complex concept double negative, और कभी-कभी triple negative में लिपटा हुआ है।
fn(...)जैसी चीज देखते ही “ये क्या है?” लगता है, औरunsafepin projection तक पहुंचते-पहुंचते बात छूट जाती हैकब safe है और कब unsafe, समझ नहीं आता, और बस हाथ खड़े कर देने का मन होता है
Move-रहित Rust सेMoveवाले Rust में जाना असुविधाजनक होगाअब तक लिखी गई लगभग हर struct में
#[derive(Move)]जोड़ना पड़ेगा, औरstdके साथ भी यही होगापुराने editions में जिन types को pinned नहीं किया गया है, उनके लिए compiler को
Movetrait implementation infer करना पड़ेगाmechanically यह संभव होगा, बस काम बहुत ज्यादा होगा
async Rust बहुत खराब है। खासकर लगभग हर दूसरी language के future/promise से तुलना करें तो
किसी दिन कोई Rust के memory safety model को बेहतर बनाएगा और
Movetrait व बेहतर futures वाली नई Rust-जैसी system language बनाएगानिजी तौर पर, मैं Rust के macro system की जगह compile-time execution भी चाहूंगा
मुझे Rust पसंद है, और टीम ने सालों में जो भी काम किया है वह भी पसंद है
लेकिन जिस language का मुझे सच में इंतजार है, वह Rust के बाद आने वाली language है
वही ideas, लेकिन Rust की गलतियों से सीखी हुई language; और ऐसी बेहतर Rust-शैली की language कैसी दिखेगी, यह धीरे-धीरे ज्यादा साफ होता जा रहा है
सच में इंतजार है
WithoutBoats का एक और बेहतरीन लेख है
सच कहूं तो यह Rust में उन हिस्सों में से एक लगता है जिनका async runtime के अंदर abstraction में दबे रहना अच्छी बात है
फिर भी custom
Futureimplementation के अलावा Pin असल में कहां इस्तेमाल होता है, यह जानने की उत्सुकता हैwording देखकर लगता है कि शायद इसे FFI के अंदर इस्तेमाल किया जा सकता है
उदाहरण के लिए अगर कोई
externfunction*mut Treturn करके pointer लेता है, तो लगता है कि उसेPin<&mut T>में wrap करके बेहतर semantics दिए जा सकते हैंलेकिन लेख कहता है, “pinned type state के बारे में एक और तथ्य यह है कि यह ज्यादातर types के लिए पूरी तरह irrelevant है। अगर किसी type की value कभी self-reference contain नहीं कर सकती, तो उसे pin करना बेकार है”
मैं FFI में अभी बहुत beginner हूं, इसलिए समझना चाहता हूं कि इसे safe Rust में wrap करने का सबसे अच्छा तरीका क्या है
address पर निर्भर system types के साथ interact करते समय भी ऐसा होता है
उदाहरण के लिए कुछ operating systems के mutex/futex में kernel docs कहते हैं कि user-space lock object का address initialization के बाद बदलना नहीं चाहिए, इसलिए मेरी जानकारी में
stdPin के equivalent कुछ इस्तेमाल करता हैअजीब बात यह है कि lock न होने पर भी address नहीं बदलना चाहिए
आम तौर पर condition सिर्फ locked रहने के दौरान होती है, और उस case में referenced target को move नहीं किया जा सकता, इसलिए Pin की जरूरत नहीं होती
असली समस्या यानी threads की अक्षमता को ठीक न करने के लिए बहुत बड़ा काम किया जा रहा है, ऐसा लगता है
सभी async code, बिना किसी अपवाद के, state management के लिए ढेर सारी syntactic sugar के साथ lightweight threads लागू करने का एक hack है
Rust जैसी language में यह ऐसी भारी complexity जोड़ देता है जिसकी मूल रूप से ज़रूरत नहीं होनी चाहिए
threads की efficiency और scalability की समस्याएँ ठीक कर दी जाएँ तो यह सब गायब हो जाएगा
बस, छू-मंतर
यह कुछ वैसा ही है जैसे Java जैसी languages में
null“ट्रिलियन-डॉलर mistake” थाएक design decision, या यहाँ design की गैर-मौजूदगी, के कारण बहुत बड़ी complexity पैदा हो जाती है
cancellation network applications और GUI में बहुत उपयोगी है
threads ऐसा बनाना मुश्किल कर देते हैं कि CPU और network दोनों का पर्याप्त उपयोग हो, लेकिन कोई भी जरूरत से ज्यादा occupy न हो
जब आप thread pools के बीच काम pass करना शुरू करते हैं, तो आप futures को दोबारा implement करने की राह पर चल पड़े होते हैं
वरना callback/event के साथ काम करना पड़ता है, जहाँ code टुकड़ों में बिखर जाता है, और
async/awaitवही syntactic sugar बनने की कोशिश कर रहा थाcancellation और timeout का alternative Go की तरह
Contextobject को पूरे code में पिरोना है, लेकिन फिर समस्या आती है कि leaf code मासूमियत से ऐसी function call कर देता है जोContextका ठीक से पालन नहीं करतीयह async code के अंदर non-async functions वाली समस्या से बस थोड़ा ही बेहतर है
इसलिए जिज्ञासा है कि ठोस रूप से क्या किया जाना चाहिए था
क्या बस हाथ खड़े कर के कहना चाहिए था, “कभी न कभी कोई Linux को ठीक करके threads को जादुई रूप से तेज बना सकता है, इसलिए हम अपनी language में async नहीं जोड़ेंगे”?
साथ ही, operating system को सभी async work का scheduler बना देने से हर runtime को operating system scheduler इस्तेमाल करना पड़ेगा, इसलिए अलग-अलग तरह के scheduler designs संभव नहीं रहेंगे
ऐसा लगता है कि “preferred approach” executable नहीं है यह सामने आने के बाद भी cost/benefit का दोबारा मूल्यांकन नहीं किया जाता
“हमें feature X चाहिए, नतीजों से मतलब नहीं” language design में शायद ही कभी जीतने वाली चाल होती है