- Ante एक systems language डिज़ाइन है, जो reference counting की लचीलापन और borrow checking की सुरक्षा को साथ इस्तेमाल करना चाहता है, जबकि Rust-शैली के runtime panic या Swift-शैली के exclusive access check overhead से बचना चाहता है
- इसके मुख्य तंत्र shape-stability और temporary uniq conversion हैं, जिनकी मदद से reference-counted value के fields पर सुरक्षित रूप से mutable borrow बनाया जा सकता है, और union के अंदर के values को केवल सीमित दायरे में
uniqकी तरह संभाला जाता है - Rust का
Rc<RefCell<T>>गलत इस्तेमाल पर runtime में panic कर सकता है, और Swift का borrowing system runtime exclusive access checks शामिल करता है, लेकिन Ante कुछ मामलों को compile-time नियमों से संभालने की कोशिश करता है - यह अभी आंशिक रूप से लागू हुआ work-in-progress डिज़ाइन है, और क्योंकि types को recursively analyze करके यह तय करना पड़ता है कि किसी खास object तक पहुँचा जा सकता है या नहीं, इसलिए field जोड़ना breaking API change बन सकता है
- यह तरीका इस धारणा को कमजोर करता है कि shared mutable borrowing हमेशा असंभव है, और Vale, group borrowing, Rust GhostCell जैसी techniques के साथ memory safety design के अपवाद-क्षेत्र को बढ़ा रहा है
Ante जिस संयोजन को हासिल करना चाहता है
- Ante एक systems programming language है, जिसका लक्ष्य memory safety और thread safety के साथ Rust का एक अधिक सरल रूप बनना है
- इसका मूल मॉडल single ownership और borrow checking है, और values stack पर या containing struct/array के अंदर inline रखे जाते हैं
- जब सरलता को प्राथमिकता देनी हो, तो type पर
sharedkeyword लगाकर reference counting चुना जा सकता है shared type Color,shared type RbTree tका उपयोग करने वाला red-black treebalancefunction Python उदाहरण जितना छोटा है, और C++ व Rust उदाहरणों से भी छोटा है- मुख्य रुचि इस बात में है कि reference-counted data को mutable रूप में borrow करते समय Rust के
borrow_mut()panic risk या Swift के runtime exclusive access checks के बिना इसे कैसे संभाला जाए - Ante अभी भी work-in-progress स्थिति में है; कुछ हिस्से implement हो चुके हैं, कुछ अभी theoretical हैं, और डिज़ाइन भी बदल रहा है
shape-stability और कई mutable references
- Ante में shape-stability का मतलब है: “stable shape वाले target के लिए reference, कहीं और कोई भी बदलाव हो, तब भी हमेशा valid रहता है”
- इस विचार की वजह से एक ही struct के लिए एक साथ कई mutable borrow references रखे जा सकते हैं
heal (healer: mut Entity) (target: mut Entity)उदाहरण में, एक हीEntityको दो arguments के रूप में देकर खुद को heal करने वालाself_healcall संभव है- चाहे
healerऔरtargetएक हीEntityको point करें, इस code मेंEntityको destroy नहीं किया जा सकता, इसलिए दोनों references valid बने रहते हैं
- चाहे
- struct खुद पर, उसके fields पर, और fields के fields पर mutable references भी एक साथ अनुमति पा सकते हैं
ship: mut Spaceshipऔरengine_alias: mut Engine = ship.engineको एक साथ इस्तेमाल करने पर भी, यह माना जाता है कि function execution के दौरानshipऔर उसके अंदर काenginedestroy नहीं होंगे
- Rust और Swift में एक ही data को एक साथ कई
&mutreferences से point करने की अनुमति नहीं है
reference-counted values के fields का mutable borrow
- Ante में type definition के आगे
sharedलगाने पर वह type अपने आप reference-counted हो जाता है shared mut type Spaceshipउदाहरण में,launchRcके समानSpaceshipको रखता है औरmut ship.engineकोset_fuelमें pass करता है- क्योंकि
launchcontaining objectSpaceshipको बनाए रखता है, इसलिए उसके fieldengineको भी जीवित माना जा सकता है - सामान्य नियम यह है कि
shared muttype के fields पर हमेशाmutborrow reference बनाया जा सकता है- लेकिन इसका मतलब यह नहीं कि उस field के अंदर मौजूद हर target पर हमेशा mutable borrow बनाया जा सके; उसके लिए अलग नियम चाहिए
- आगे के उदाहरण sugar syntax
shared mut type Spaceshipकी जगह अधिक स्पष्टRc Spaceshipnotation का उपयोग करते हैंshared mut type Spaceshipअबtype Spaceshipबन जाता है, औरvar ship: Spaceshipका अर्थvar ship: Rc Spaceshipहोता है
जहाँ union safety problem पैदा करता है
- union अपने contents को inline रख सकता है, जिससे pointer chasing और cache miss कम होते हैं, इसलिए यह performance के लिए फायदेमंद है
- अगर C का
union Engine,struct Spaceshipके अंदर हो, तोStringTheoryEngineऔरImpulseEngineदोनोंSpaceshipmemory के अंदर ही स्थित होंगे - यह Java जैसी interface-plus-pointer approach के विपरीत है
- अगर C का
- समस्या यह है कि memory-safe languages में union को safely support करना कठिन है
- उस उदाहरण में जहाँ
Engine,StringTheoryEngine(str: String)याImpulseEngine(fuel: I32)हो सकता है, यदिshipऔरother_shipएक हीSpaceshipको point करते हैं, तो segfault हो सकता है- पहले
match uniq ship.engineसे string के अंदर का reference पकड़ लिया जाता है - फिर
other_ship.engine := ImpulseEngine 0x42से उसी engine को दूसरी variant में बदल दिया जाता है - और उसके बाद पुराने
strको modify करने पर container destroy होने के बाद उसके अंदर का उपयोग करने की समस्या पैदा हो जाती है
- पहले
- इसलिए Ante को यह सुनिश्चित करना पड़ता है कि जब mutable borrow reference union को point करे, तब उसकी किसी variant के अंदर mutable borrow reference नहीं बनाया जा सके
- यह struct वाले नियम के उलट है
- struct के लिए
mutreference हो तो fields के लिएmutreference बनाया जा सकता है - union के लिए
mutreference हो तो variant के अंदरmutreference नहीं बनाया जा सकता
- struct के लिए
uniq और temporary uniq conversion
uniqका मतलब exclusive mutable reference है, यानी एकाधिकार वाला mutable reference- अगर किसी variable में
uniq Spaceshipहै, तो वही उसSpaceshipके लिए उपलब्ध एकमात्र usable reference है- यह Rust के
&mut Spaceshipजैसा विचार है
- यह Rust के
- union के अंदर को safely संभालने के लिए Ante temporary uniq conversion का उपयोग करता है
- मुख्य नियम यह है कि अगर किसी खास scope में दूसरे संभावित aliased references का उपयोग नहीं किया जाता, तो अस्थायी रूप से
uniqreference पाया जा सकता हैmatch uniq ship.engineवाले हिस्से मेंship.engineकोuniqकी तरह access किया जाता है- इस scope के दौरान compiler उन दूसरे मौजूदा variables के उपयोग को रोकता है जो अप्रत्यक्ष रूप से
Spaceshipको contain कर सकते हों
- Rust “कहीं न कहीं दूसरा reference मौजूद हो सकता है” इस कारण
uniqके अस्तित्व को ही रोकता है, जबकि Ante उस scope में उन references का उपयोग न करने की शर्त परuniqकी अनुमति देता है - यहाँ
uniq Spaceshipवास्तव में globally unique reference नहीं है, बल्कि उस scope के भीतर unique usable reference है- इसका भाव C के
restrictpointer जैसा है
- इसका भाव C के
कौन-सी access स्वीकार होगी और कौन-सी नहीं
match uniq ship.enginescope के भीतरother_ship: Rc Spaceshipको access करने पर compile error आना चाहिए- क्योंकि
other_ship.engine,ship.engineका alias हो सकता है - और
ship.engineउपयोग के दौरानother_ship.engineमें बदलाव drop trigger कर सकता है
- क्योंकि
HasAShipजैसे वे दूसरे structs भी इसी कारण reject होंगे जिनमें field के रूप मेंRc Spaceshipहोother.ship.engineभी अप्रत्यक्ष रूप से उसीSpaceshipतक पहुँच सकता है
- इसके उलट
new_fuel: I32जैसे integer values का उपयोग किया जा सकता है- क्योंकि
I32,Spaceshipके लिए reference contain नहीं कर सकता
- क्योंकि
- अगर
Spaceshipखुदfollow_ship: Rc Spaceshipजैसा field रखता है, तो वह भी reject होगा- क्योंकि तब
uniq Spaceshipअपने ही internal path से फिर से reachable हो जाता है, इसलिए सामान्य रूप से recursive types परmut -> uniqconversion संभव नहीं होता
- क्योंकि तब
function calls और return में constraints
- function call के दौरान भी
mut -> uniqconversion हो सकता है - जब
foo (var ship: Rc Spaceship) (new_res: Resonator)maybe_use_resonator ship new_resको call करता है, तो call site परshipकोuniq Spaceshipमें convert किया जाता है- compiler को बस यह जाँचना होता है कि क्या अन्य arguments
Spaceshipreferences रख सकते हैं - उदाहरण का
Resonatorऐसे references नहीं रखता, इसलिए यह अनुमति पाता है
- compiler को बस यह जाँचना होता है कि क्या अन्य arguments
- return के मामले में converted
uniqreference को सामान्यuniqके रूप में लौटाया नहीं जा सकता- क्योंकि return के बाद compiler की यह जाँच लागू नहीं रहती कि “इस scope में alias हो सकने वाले variables का उपयोग न किया जाए”
- इसके बदले return type को
local uniq Fooलिखा जा सकता है- आंतरिक रूप से
mut refसेuniq refconversion होने पर वास्तव में हमेशा local uniq ही बनता है - अधिकांश मामलों में इसे सामान्य
uniqकी तरह इस्तेमाल किया जा सकता है, लेकिन return करते समय इसे स्पष्ट लिखना पड़ता है
- आंतरिक रूप से
डिज़ाइन की लागत और विकल्प
- Ante,
Rc Spaceshipजैसे reference-counted reference को runtime error के बिना अस्थायीuniq Spaceshipमें बदल सकता है - इसकी कमी यह है कि compiler को “क्या
EngineसेSpaceshipतक पहुँचा जा सकता है?” जैसे सवालों के उत्तर देने के लिए types को recursively देखना पड़ता है - ऐसा analysis नाजुक हो सकता है
- struct में field जोड़ना breaking API change बन सकता है
- Ante के निर्माता Jake इस guarantee को बनाए रखने का बेहतर तरीका ढूँढ रहे हैं
- group borrowing और Flix references की तरह हर shared mutable type को anonymous unique brand type देना
- shared type बदलते समय
Mutates 'aजैसे effect जोड़कर type analysis हटाना - runtime पर user से यह जँचवाना कि दो references अलग objects को point करते हैं, या unsafe checks को safe API में wrap करके देना
- compiler द्वारा उन values को track करना जो
Rcके अंदर अप्रत्यक्ष रूप से store नहीं होतीं और इसलिए alias नहीं हो सकतीं
- Pony के iso permission जैसा विचार, या ऐसे temporary permissions जिनमें struct के अंदर देखा जा सके लेकिन बाहर point करने वाले references का उपयोग न किया जा सके, अभी भी संभावित विकल्प हैं
- कठिनाई यह है कि इतनी लचीलापन बनाए रखते हुए Ante के लक्ष्यों—usability, readability, और simplicity—को भी बचाए रखा जाए
memory safety की व्यापक दिशा
- shared mutable borrowing को पहले असंभव माना जाता था, और पृष्ठभूमि में यह दृष्टिकोण है कि Rust भी इसी विश्वास पर डिज़ाइन हुआ था
- अब कई अपवाद जमा हो रहे हैं
- Ante local uniqueness नियमों के जरिए shared-mutable data से
uniqborrow reference पा सकता है - Vale pure function के जरिए shared-mutable data से immutable borrow reference पा सकता है
- group borrowing shape-stable न होने पर भी shared-mutable borrow reference बना सकता है
- Rust का GhostCell object graph को आपस में स्वतंत्र रूप से point करने देता है, लेकिन किसी खास समय पर उनमें से केवल एक के लिए mutable reference रखा जा सकता है
- Ante local uniqueness नियमों के जरिए shared-mutable data से
- यह प्रवृत्ति संकेत देती है कि memory safety design में shared mutable borrowing को संभालने का कोई अधिक सामान्य सिद्धांत हो सकता है
Rust Cell से तुलना
- Rust उपयोगकर्ता पूछ सकते हैं कि struct fields में
Cellरखने की पद्धति और Ante की approach में क्या फर्क है - Ante के उदाहरण में
Rc Spaceshipसेstatus: Stringके लिएmut Stringreference लेकर उसमें सीधे" (refueling)"जोड़ा जा सकता है - Rust के
Cell<String>approach मेंRc<Spaceship>से&mut Stringनहीं मिल सकता- इसकी जगह
status_ref.replace(String::new())से अस्थायी default value डालनी पड़ती है - फिर निकाले गए
Stringको modify करना पड़ता है - और आखिर में
replace(status)से उसे वापस रखना पड़ता है
- इसकी जगह
- इस तरीके की कुछ कमियाँ हैं
""जैसी default instance बनानी पड़ती है- आखिरी
replacecall भूल जाने का जोखिम रहता है - value replace हुई हो, उस दौरान कोई
statusपढ़ ले, इसका भी जोखिम रहता है
- Ante अस्थायी रूप से
statusstring के लिए reference लेने देता है, और उस दौरान compiler यह सुनिश्चित करता है कि दूसरा code उसे access न कर सके
1 टिप्पणियां
Lobste.rs की राय
“shared mutable borrow” को असंभव मानना सिर्फ Rust द्वारा अपना लक्ष्य हासिल करने के लिए दी गई कोई कुर्बानी नहीं था, बल्कि यह Rust के core goals के बहुत करीब है
क्योंकि shared mutable state, code के बारे में local reasoning को मुश्किल बना देती है
"References are like jumps" by withoutboats इस बात को अच्छी तरह कवर करता है। तर्क यह है कि aliased state में अनजाने बदलाव रोकना, सही तरह से काम करने वाले systems बनाना आसान करने की कुंजी है, और Rust के lifetime rules सिर्फ garbage collection से बचने का उपाय नहीं हैं, बल्कि ऐसी language में reasoning संभव बनाए रखने की गहरी संरचना हैं जो mutable state और aliased state दोनों को साथ में अनुमति देती है
काफी अच्छा लग रहा है
अगर मेरी समझ सही है, तो shared reference से mutable reference पर जाने वाला जादू इसलिए संभव है क्योंकि यह उन types तक सीमित है जो threads के बीच share नहीं होते, और
Rcकी uniqueness शायद इस तरह guarantee की जाती है कि उसी type के सभी objects को उसी lifetime के साथ borrow किया हुआ माना जाता हैexplicit syntax और natural syntax में से क्या बेहतर है, यह पसंद का मामला हो सकता है, लेकिन यह दिखाता है कि compiler अगर
Cellके बारे में ज्यादा जानता हो तो उसके लिए mutable references को ज्यादा flexible तरीके से allow कर सकता हैऔर Rust में
mutको mutable नहीं बल्कि exclusive/unique के अर्थ में इस्तेमाल किए जाने जैसी confusing terminology से भी बचता हैuniqpromotion का मतलब lock acquire करना है?” लेकिन अब समझ आता है कि comparisonRcसे है,Arcसे नहींmutका मतलब exclusive/unique कैसे है?अंत में जिस unifying principle की ओर इशारा किया गया है, वह क्या हो सकता है—क्या किसी को अंदाजा है?
antelang.org blog posts पर पिछली चर्चाएँ भी देखने लायक हैं
मुझे ठीक से समझ नहीं आ रहा कि यह कैसे काम करता है। ऐसा लगता है कि इसका मतलब है, “अगर आपके पास किसी object का mutable pointer है, तो आप उस object के slice का mutable reference पा सकते हैं”
लेकिन अगर ऐसा है, तो उदाहरण के लिए
mutref someobjext = …,mutref subfield = someobjext.a.b,someobjext.a = somethingelseजैसा कुछ संभव दिखता है, और फिरsubfieldinvalid हो सकता है या value बदलने पर टूट सकता हैलेख में explanations, दूसरी languages से comparisons और code examples तो काफी थे, लेकिन इस behavior की step-by-step semantics को बुनियादी रूप से व्यवस्थित करने वाला हिस्सा ढूँढना मुश्किल लगा