1 पॉइंट द्वारा GN⁺ 4 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 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 पर shared keyword लगाकर reference counting चुना जा सकता है
  • shared type Color, shared type RbTree t का उपयोग करने वाला red-black tree balance function 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_heal call संभव है
    • चाहे 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 और उसके अंदर का engine destroy नहीं होंगे
  • Rust और Swift में एक ही data को एक साथ कई &mut references से point करने की अनुमति नहीं है

reference-counted values के fields का mutable borrow

  • Ante में type definition के आगे shared लगाने पर वह type अपने आप reference-counted हो जाता है
  • shared mut type Spaceship उदाहरण में, launch Rc के समान Spaceship को रखता है और mut ship.engine को set_fuel में pass करता है
  • क्योंकि launch containing object Spaceship को बनाए रखता है, इसलिए उसके field engine को भी जीवित माना जा सकता है
  • सामान्य नियम यह है कि shared mut type के fields पर हमेशा mut borrow reference बनाया जा सकता है
    • लेकिन इसका मतलब यह नहीं कि उस field के अंदर मौजूद हर target पर हमेशा mutable borrow बनाया जा सके; उसके लिए अलग नियम चाहिए
  • आगे के उदाहरण sugar syntax shared mut type Spaceship की जगह अधिक स्पष्ट Rc Spaceship notation का उपयोग करते हैं
    • 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 दोनों Spaceship memory के अंदर ही स्थित होंगे
    • यह Java जैसी interface-plus-pointer approach के विपरीत है
  • समस्या यह है कि 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 के लिए mut reference हो तो fields के लिए mut reference बनाया जा सकता है
    • union के लिए mut reference हो तो variant के अंदर mut reference नहीं बनाया जा सकता

uniq और temporary uniq conversion

  • uniq का मतलब exclusive mutable reference है, यानी एकाधिकार वाला mutable reference
  • अगर किसी variable में uniq Spaceship है, तो वही उस Spaceship के लिए उपलब्ध एकमात्र usable reference है
    • यह Rust के &mut Spaceship जैसा विचार है
  • union के अंदर को safely संभालने के लिए Ante temporary uniq conversion का उपयोग करता है
  • मुख्य नियम यह है कि अगर किसी खास scope में दूसरे संभावित aliased references का उपयोग नहीं किया जाता, तो अस्थायी रूप से uniq reference पाया जा सकता है
    • 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 के restrict pointer जैसा है

कौन-सी access स्वीकार होगी और कौन-सी नहीं

  • match uniq ship.engine scope के भीतर 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 -> uniq conversion संभव नहीं होता

function calls और return में constraints

  • function call के दौरान भी mut -> uniq conversion हो सकता है
  • जब foo (var ship: Rc Spaceship) (new_res: Resonator) maybe_use_resonator ship new_res को call करता है, तो call site पर ship को uniq Spaceship में convert किया जाता है
    • compiler को बस यह जाँचना होता है कि क्या अन्य arguments Spaceship references रख सकते हैं
    • उदाहरण का Resonator ऐसे references नहीं रखता, इसलिए यह अनुमति पाता है
  • return के मामले में converted uniq reference को सामान्य uniq के रूप में लौटाया नहीं जा सकता
    • क्योंकि return के बाद compiler की यह जाँच लागू नहीं रहती कि “इस scope में alias हो सकने वाले variables का उपयोग न किया जाए”
  • इसके बदले return type को local uniq Foo लिखा जा सकता है
    • आंतरिक रूप से mut ref से uniq ref conversion होने पर वास्तव में हमेशा 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 से uniq borrow 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 रखा जा सकता है
  • यह प्रवृत्ति संकेत देती है कि memory safety design में shared mutable borrowing को संभालने का कोई अधिक सामान्य सिद्धांत हो सकता है

Rust Cell से तुलना

  • Rust उपयोगकर्ता पूछ सकते हैं कि struct fields में Cell रखने की पद्धति और Ante की approach में क्या फर्क है
  • Ante के उदाहरण में Rc Spaceship से status: String के लिए mut String reference लेकर उसमें सीधे " (refueling)" जोड़ा जा सकता है
  • Rust के Cell<String> approach में Rc<Spaceship> से &mut String नहीं मिल सकता
    • इसकी जगह status_ref.replace(String::new()) से अस्थायी default value डालनी पड़ती है
    • फिर निकाले गए String को modify करना पड़ता है
    • और आखिर में replace(status) से उसे वापस रखना पड़ता है
  • इस तरीके की कुछ कमियाँ हैं
    • "" जैसी default instance बनानी पड़ती है
    • आखिरी replace call भूल जाने का जोखिम रहता है
    • value replace हुई हो, उस दौरान कोई status पढ़ ले, इसका भी जोखिम रहता है
  • Ante अस्थायी रूप से status string के लिए reference लेने देता है, और उस दौरान compiler यह सुनिश्चित करता है कि दूसरा code उसे access न कर सके

1 टिप्पणियां

 
GN⁺ 4 시간 전
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 दोनों को साथ में अनुमति देती है

    • पहले जब लेखक ने Mojo borrow checker पर बात की थी, तब भी यही लगा था। Rust का borrow checker single-threaded programs में भी value semantics बनाए रखता है
  • काफी अच्छा लग रहा है
    अगर मेरी समझ सही है, तो 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 से भी बचता है

    • मैं सोच रहा था कि threads के बीच क्या होगा। सवाल ऐसा था कि “क्या uniq promotion का मतलब lock acquire करना है?” लेकिन अब समझ आता है कि comparison Rc से है, 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 जैसा कुछ संभव दिखता है, और फिर subfield invalid हो सकता है या value बदलने पर टूट सकता है
    लेख में explanations, दूसरी languages से comparisons और code examples तो काफी थे, लेकिन इस behavior की step-by-step semantics को बुनियादी रूप से व्यवस्थित करने वाला हिस्सा ढूँढना मुश्किल लगा