paint-brush
रस्ट का स्वामित्व और उधार स्मृति सुरक्षा को लागू करता हैद्वारा@senthilnayagan
2,203 रीडिंग
2,203 रीडिंग

रस्ट का स्वामित्व और उधार स्मृति सुरक्षा को लागू करता है

द्वारा Senthil Nayagan31m2022/07/15
Read on Terminal Reader
Read this story w/o Javascript

बहुत लंबा; पढ़ने के लिए

यदि हम यह नहीं समझ पाते हैं कि वास्तव में क्या हो रहा है, तो रस्ट का स्वामित्व और उधार लेना भ्रमित करने वाला हो सकता है। यह विशेष रूप से सच है जब पहले से सीखी गई प्रोग्रामिंग शैली को एक नए प्रतिमान में लागू करना; हम इसे एक प्रतिमान बदलाव कहते हैं। यदि कोई प्रोग्राम वास्तव में मेमोरी सुरक्षित नहीं है, तो इसकी कार्यक्षमता के बारे में कुछ आश्वासन हैं।

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - रस्ट का स्वामित्व और उधार स्मृति सुरक्षा को लागू करता है
Senthil Nayagan HackerNoon profile picture

रस्ट का स्वामित्व और उधारी भ्रमित करने वाली हो सकती है यदि हम यह नहीं समझ पाते हैं कि वास्तव में क्या हो रहा है। यह विशेष रूप से सच है जब पहले से सीखी गई प्रोग्रामिंग शैली को एक नए प्रतिमान में लागू किया जाता है; हम इसे एक प्रतिमान बदलाव कहते हैं। स्वामित्व एक नया विचार है, फिर भी पहली बार में समझना मुश्किल है, लेकिन जितना अधिक हम इस पर काम करते हैं, यह आसान हो जाता है।


इससे पहले कि हम रस्ट के स्वामित्व और उधार के बारे में आगे बढ़ें, आइए पहले समझते हैं कि "मेमोरी सेफ्टी" और "मेमोरी लीक" क्या हैं और प्रोग्रामिंग भाषाएं उनसे कैसे निपटती हैं।

मेमोरी सेफ्टी क्या है?

मेमोरी सेफ्टी एक सॉफ्टवेयर एप्लिकेशन की स्थिति को संदर्भित करता है जहां मेमोरी पॉइंटर्स या संदर्भ हमेशा वैध मेमोरी को संदर्भित करते हैं। चूंकि स्मृति भ्रष्टाचार एक संभावना है, इसलिए प्रोग्राम के व्यवहार के बारे में बहुत कम गारंटी है यदि यह स्मृति सुरक्षित नहीं है। सीधे शब्दों में कहें, यदि कोई प्रोग्राम वास्तव में मेमोरी सुरक्षित नहीं है, तो इसकी कार्यक्षमता के बारे में कुछ आश्वासन हैं। मेमोरी-असुरक्षित प्रोग्राम के साथ काम करते समय, एक दुर्भावनापूर्ण पार्टी किसी अन्य की मशीन पर रहस्यों को पढ़ने या मनमाने कोड को निष्पादित करने के लिए दोष का उपयोग करने में सक्षम होती है।


फ्रीपिक द्वारा डिज़ाइन किया गया।


आइए एक स्यूडोकोड का उपयोग करके देखें कि वैध मेमोरी क्या है।


 // pseudocode #1 - shows valid reference { // scope starts here int x = 5 int y = &x } // scope ends here


उपरोक्त छद्म कोड में, हमने 10 के मान के साथ असाइन किया गया एक चर x बनाया है। हम संदर्भ बनाने के लिए & ऑपरेटर या कीवर्ड का उपयोग करते हैं। इस प्रकार, &x सिंटैक्स हमें एक संदर्भ बनाने देता है जो x के मान को संदर्भित करता है। सीधे शब्दों में कहें तो, हमने एक वैरिएबल x बनाया है जो 5 का मालिक है और एक वेरिएबल y जो कि x का संदर्भ है।


चूंकि दोनों चर x और y एक ही ब्लॉक या दायरे में हैं, चर y का एक वैध संदर्भ है जो x के मान को संदर्भित करता है। परिणामस्वरूप, चर y का मान 5 है।


नीचे दिए गए स्यूडोकोड पर एक नज़र डालें। जैसा कि हम देख सकते हैं, x का दायरा उस ब्लॉक तक सीमित है जिसमें इसे बनाया गया है। जब हम x को इसके दायरे से बाहर एक्सेस करने का प्रयास करते हैं तो हम झूलने वाले संदर्भों में आ जाते हैं। लटकता हुआ संदर्भ ...? आख़िर यह क्या है?


 // pseudocode #2 - shows invalid reference aka dangling reference { // scope starts here int x = 5 } // scope ends here int y = &x // can't access x from here; creates dangling reference


लटकता हुआ संदर्भ

एक लटकता हुआ संदर्भ एक सूचक है जो एक स्मृति स्थान को इंगित करता है जो किसी और को दिया गया है या जारी किया गया है (मुक्त)। यदि कोई प्रोग्राम (उर्फ प्रक्रिया ) स्मृति को संदर्भित करता है जिसे जारी किया गया है या मिटा दिया गया है, तो यह क्रैश हो सकता है या गैर-निर्धारक परिणाम उत्पन्न कर सकता है।


ऐसा कहने के बाद, स्मृति असुरक्षितता कुछ प्रोग्रामिंग भाषाओं की एक संपत्ति है जो प्रोग्रामर को अमान्य डेटा से निपटने की अनुमति देती है। नतीजतन, स्मृति असुरक्षितता ने कई तरह की समस्याएं पेश कीं जो निम्नलिखित प्रमुख सुरक्षा कमजोरियों का कारण बन सकती हैं:


  • आउट-ऑफ-बाउंड पढ़ता है
  • आउट-ऑफ-बाउंड लिखता है
  • का उपयोग के बाद नि: शुल्क


स्मृति असुरक्षितता के कारण होने वाली भेद्यताएं कई अन्य गंभीर सुरक्षा खतरों की जड़ में हैं। दुर्भाग्य से, इन कमजोरियों को उजागर करना डेवलपर्स के लिए बेहद चुनौतीपूर्ण हो सकता है।

मेमोरी लीक क्या है?

यह समझना महत्वपूर्ण है कि मेमोरी लीक क्या है और इसके परिणाम क्या हैं।


फ्रीपिक द्वारा डिज़ाइन किया गया।


एक स्मृति रिसाव स्मृति खपत का एक अनजाने रूप है जिससे डेवलपर ढेर स्मृति के आवंटित ब्लॉक को मुक्त करने में विफल रहता है जब इसकी आवश्यकता नहीं होती है। यह स्मृति सुरक्षा के बिल्कुल विपरीत है। विभिन्न मेमोरी प्रकारों के बारे में बाद में, लेकिन अभी के लिए, बस यह जान लें कि एक स्टैक निश्चित-लंबाई वाले चर को संकलित समय पर संग्रहीत करता है, जबकि चर का आकार जो बाद में रनटाइम पर बदल सकता है, उसे ढेर पर रखा जाना चाहिए।


हीप मेमोरी आवंटन की तुलना में, स्टैक मेमोरी आवंटन को सुरक्षित माना जाता है क्योंकि मेमोरी स्वचालित रूप से रिलीज़ हो जाती है जब यह अब प्रासंगिक या आवश्यक नहीं है, या तो प्रोग्रामर द्वारा या प्रोग्राम-रनटाइम द्वारा।


हालांकि, जब प्रोग्रामर ढेर पर मेमोरी उत्पन्न करते हैं और कचरा संग्रहकर्ता (सी और सी ++ के मामले में) की अनुपस्थिति में इसे हटाने में विफल होते हैं, तो स्मृति रिसाव विकसित होता है। साथ ही, अगर हम उस स्मृति को हटाए बिना स्मृति के एक हिस्से के सभी संदर्भ खो देते हैं, तो हमारे पास स्मृति रिसाव है। हमारा प्रोग्राम उस मेमोरी का स्वामी बना रहेगा, लेकिन उसके पास इसे फिर से उपयोग करने का कोई तरीका नहीं है।


थोड़ी सी मेमोरी लीक कोई समस्या नहीं है, लेकिन अगर कोई प्रोग्राम बड़ी मात्रा में मेमोरी आवंटित करता है और इसे कभी नहीं हटाता है, तो प्रोग्राम की मेमोरी फ़ुटप्रिंट बढ़ती रहेगी, जिसके परिणामस्वरूप सेवा से इनकार किया जा सकता है।


जब कोई प्रोग्राम बाहर निकलता है, तो ऑपरेटिंग सिस्टम उसके पास मौजूद सभी मेमोरी को तुरंत रिकवर कर लेता है। नतीजतन, एक स्मृति रिसाव केवल एक प्रोग्राम को प्रभावित करता है जब वह चल रहा हो; कार्यक्रम समाप्त होने के बाद इसका कोई प्रभाव नहीं पड़ता है।


आइए मेमोरी लीक के प्रमुख परिणामों पर चलते हैं।


मेमोरी लीक उपलब्ध मेमोरी (हीप मेमोरी) की मात्रा को कम करके कंप्यूटर के प्रदर्शन को कम करता है। यह अंततः पूरे या सिस्टम के एक हिस्से को सही ढंग से काम करना बंद कर देता है या गंभीर रूप से धीमा कर देता है। क्रैश आमतौर पर मेमोरी लीक से जुड़े होते हैं।


मेमोरी लीक को रोकने के तरीके का पता लगाने के लिए हमारा दृष्टिकोण उस प्रोग्रामिंग भाषा के आधार पर अलग-अलग होगा जिसका हम उपयोग कर रहे हैं। मेमोरी लीक एक छोटी और लगभग "अनदेखी समस्या" के रूप में शुरू हो सकता है, लेकिन वे बहुत तेज़ी से बढ़ सकते हैं और सिस्टम को प्रभावित कर सकते हैं। जहां भी संभव हो, हमें उनकी तलाश में रहना चाहिए और उन्हें बढ़ने के बजाय उन्हें सुधारने के लिए कार्रवाई करनी चाहिए।

मेमोरी असुरक्षित बनाम मेमोरी लीक

मेमोरी लीक और मेमोरी असुरक्षित दो प्रकार के मुद्दे हैं जिन पर रोकथाम और उपचार के मामले में सबसे अधिक ध्यान दिया गया है। यह ध्यान रखना महत्वपूर्ण है कि एक को ठीक करने से दूसरे को स्वचालित रूप से ठीक नहीं किया जाता है।


चित्र 1: मेमोरी असुरक्षितता बनाम मेमोरी लीक।


विभिन्न प्रकार की यादें और वे कैसे काम करती हैं

इससे पहले कि हम आगे बढ़ें, विभिन्न प्रकार की मेमोरी को समझना महत्वपूर्ण है जो हमारा कोड रनटाइम पर उपयोग करेगा।


स्मृति दो प्रकार की होती है, जो इस प्रकार हैं, और इन स्मृतियों की संरचना अलग-अलग होती है।

  • प्रोसेसर रजिस्टर

  • स्थिर

  • ढेर

  • ढेर


प्रोसेसर रजिस्टर और स्थिर मेमोरी प्रकार दोनों इस पोस्ट के दायरे से बाहर हैं।

स्टैक मेमोरी और यह कैसे काम करता है

स्टैक डेटा को प्राप्त होने के क्रम में संग्रहीत करता है और इसे उल्टे क्रम में हटा देता है। आइटम को स्टैक से लास्ट इन, फर्स्ट आउट (LIFO) ऑर्डर में एक्सेस किया जा सकता है। स्टैक पर डेटा जोड़ने को "पुशिंग" कहा जाता है और स्टैक से डेटा को हटाने को "पॉपिंग" कहा जाता है।


स्टैक पर संग्रहीत सभी डेटा का एक ज्ञात, निश्चित आकार होना चाहिए। संकलन समय पर अज्ञात आकार वाले डेटा या बाद में बदल सकने वाले आकार को इसके बजाय हीप पर संग्रहीत किया जाना चाहिए।


डेवलपर्स के रूप में, हमें स्टैक मेमोरी आवंटन और डीलोकेशन के बारे में चिंता करने की ज़रूरत नहीं है; स्टैक मेमोरी का आवंटन और डीलोकेशन कंपाइलर द्वारा "स्वचालित रूप से किया जाता है"। इसका तात्पर्य यह है कि जब स्टैक पर डेटा अब प्रासंगिक (दायरे से बाहर) नहीं है, तो यह हमारे हस्तक्षेप की आवश्यकता के बिना स्वचालित रूप से हटा दिया जाता है।


इस प्रकार की मेमोरी आवंटन को अस्थायी मेमोरी आवंटन के रूप में भी जाना जाता है, क्योंकि जैसे ही फ़ंक्शन अपना निष्पादन समाप्त करता है, उस फ़ंक्शन से संबंधित सभी डेटा "स्वचालित रूप से" स्टैक से बाहर निकल जाता है।


जंग में सभी आदिम प्रकार ढेर पर रहते हैं। संख्याएं, वर्ण, स्लाइस, बूलियन, निश्चित आकार के सरणी, आदिम युक्त टुपल्स, और फ़ंक्शन पॉइंटर्स जैसे प्रकार सभी स्टैक पर बैठ सकते हैं।


हीप मेमोरी और यह कैसे काम करता है

स्टैक के विपरीत, जब हम ढेर पर डेटा डालते हैं, तो हम एक निश्चित मात्रा में स्थान का अनुरोध करते हैं। मेमोरी आवंटक ढेर में एक बड़ी पर्याप्त खाली जगह का पता लगाता है, इसे उपयोग के रूप में चिह्नित करता है, और उस स्थान के पते का संदर्भ देता है। इसे आवंटन कहा जाता है।


ढेर पर आवंटन ढेर को धक्का देने से धीमा है क्योंकि आवंटक को नया डेटा डालने के लिए खाली स्थान की तलाश नहीं करनी पड़ती है। इसके अलावा, क्योंकि हमें ढेर पर डेटा प्राप्त करने के लिए एक पॉइंटर का पालन करना चाहिए, यह स्टैक पर डेटा तक पहुंचने से धीमा है। स्टैक के विपरीत, जिसे संकलन समय पर आवंटित और हटा दिया जाता है, हीप मेमोरी को प्रोग्राम के निर्देशों के निष्पादन के दौरान आवंटित और हटा दिया जाता है।


कुछ प्रोग्रामिंग भाषाओं में, हीप मेमोरी आवंटित करने के लिए, हम कीवर्ड new का उपयोग करते हैं। यह new कीवर्ड (उर्फ ऑपरेटर ) ढेर पर स्मृति आवंटन के अनुरोध को दर्शाता है। यदि ढेर पर पर्याप्त स्मृति उपलब्ध है, तो new ऑपरेटर स्मृति को प्रारंभ करता है और उस नई आवंटित स्मृति का अद्वितीय पता देता है।


यह ध्यान देने योग्य है कि हीप मेमोरी को प्रोग्रामर या रनटाइम द्वारा "स्पष्ट रूप से" हटा दिया जाता है।

विभिन्न अन्य प्रोग्रामिंग भाषाएँ स्मृति सुरक्षा की गारंटी कैसे देती हैं?

जब स्मृति प्रबंधन की बात आती है, विशेष रूप से मेमोरी को ढेर करने की, तो हम अपनी प्रोग्रामिंग भाषाओं को निम्नलिखित विशेषताओं के लिए पसंद करेंगे:

  • हम जितनी जल्दी हो सके स्मृति को जारी करना पसंद करेंगे जब इसकी आवश्यकता नहीं होगी, बिना रनटाइम ओवरहेड के।
  • हमें कभी भी किसी ऐसे डेटा का संदर्भ नहीं रखना चाहिए जिसे मुक्त कर दिया गया हो (उर्फ एक लटकता हुआ संदर्भ)। अन्यथा, क्रैश और सुरक्षा समस्याएँ हो सकती हैं।


प्रोग्रामिंग भाषाओं द्वारा विभिन्न तरीकों से मेमोरी सुरक्षा सुनिश्चित की जाती है:

  • स्पष्ट मेमोरी डीलोकेशन (सी, सी ++ द्वारा अपनाया गया)
  • स्वचालित या अंतर्निहित मेमोरी डीलोकेशन (जावा, पायथन और सी # द्वारा अपनाया गया)
  • क्षेत्र आधारित स्मृति प्रबंधन
  • रैखिक या अद्वितीय प्रकार की प्रणालियाँ


क्षेत्र-आधारित स्मृति प्रबंधन और रैखिक प्रकार प्रणाली दोनों इस पद के दायरे से बाहर हैं।

मैनुअल या स्पष्ट मेमोरी डीललोकेशन

प्रोग्रामर को स्पष्ट स्मृति प्रबंधन का उपयोग करते समय आवंटित स्मृति को "मैन्युअल रूप से" जारी या मिटा देना चाहिए। एक "डीलोकेशन" ऑपरेटर (उदाहरण के लिए, सी में delete ) स्पष्ट मेमोरी डीलोकेशन वाली भाषाओं में मौजूद है।


सी और सी ++ जैसी सिस्टम भाषाओं में कचरा संग्रह बहुत महंगा है, इसलिए स्पष्ट स्मृति आवंटन मौजूद है।


स्मृति को मुक्त करने की जिम्मेदारी प्रोग्रामर पर छोड़ने से प्रोग्रामर को चर के जीवन चक्र पर पूर्ण नियंत्रण देने का लाभ मिलता है। हालाँकि, यदि डीललोकेशन ऑपरेटरों का गलत तरीके से उपयोग किया जाता है, तो निष्पादन के दौरान एक सॉफ़्टवेयर त्रुटि हो सकती है। वास्तव में, यह मैनुअल आवंटन और जारी करने की प्रक्रिया त्रुटियों से ग्रस्त है। कुछ सामान्य कोडिंग त्रुटियों में शामिल हैं:

  • लटकता हुआ संदर्भ
  • स्मृति रिसाव


इसके बावजूद, हमने कचरा संग्रहण पर मैनुअल मेमोरी प्रबंधन को प्राथमिकता दी क्योंकि यह हमें अधिक नियंत्रण देता है और बेहतर प्रदर्शन प्रदान करता है। ध्यान दें कि किसी भी सिस्टम प्रोग्रामिंग भाषा का लक्ष्य जितना संभव हो "धातु के करीब" प्राप्त करना है। दूसरे शब्दों में, वे ट्रेडऑफ़ में सुविधा सुविधाओं पर बेहतर प्रदर्शन का पक्ष लेते हैं।


यह पूरी तरह से हमारी (डेवलपर्स) जिम्मेदारी है कि हम यह सुनिश्चित करें कि हमारे द्वारा मुक्त किए गए मूल्य का कोई संकेतक कभी भी उपयोग नहीं किया जाता है।


हाल के दिनों में, इन त्रुटियों से बचने के लिए कई सिद्ध पैटर्न रहे हैं, लेकिन यह सब कठोर कोड अनुशासन बनाए रखने के लिए उबलता है, जिसके लिए लगातार सही मेमोरी प्रबंधन पद्धति को लागू करने की आवश्यकता होती है।


प्रमुख टेकअवे हैं:

  • स्मृति प्रबंधन पर अधिक नियंत्रण रखें।
  • लटकते संदर्भों और मेमोरी लीक के परिणामस्वरूप कम सुरक्षा।
  • लंबे विकास समय में परिणाम।

स्वचालित या अंतर्निहित मेमोरी डीललोकेशन

जावा सहित सभी आधुनिक प्रोग्रामिंग भाषाओं की स्वचालित स्मृति प्रबंधन एक अनिवार्य विशेषता बन गई है।


स्वचालित मेमोरी डीलोकेशन के मामले में, कचरा संग्रहकर्ता स्वचालित मेमोरी मैनेजर के रूप में कार्य करता है। ये कचरा संग्रहकर्ता समय-समय पर मेमोरी के ढेर और रीसायकल के माध्यम से जाते हैं जिनका उपयोग नहीं किया जा रहा है। वे हमारी ओर से स्मृति के आवंटन और रिलीज का प्रबंधन करते हैं। इसलिए हमें स्मृति प्रबंधन कार्यों को करने के लिए कोड लिखने की आवश्यकता नहीं है। यह बहुत अच्छा है क्योंकि कचरा संग्रहकर्ता हमें स्मृति प्रबंधन की जिम्मेदारी से मुक्त करते हैं। एक और फायदा यह है कि यह विकास के समय को कम करता है।


दूसरी ओर, कचरा संग्रह में कई कमियां हैं। कचरा संग्रहण के दौरान, कार्यक्रम को रुकना चाहिए और आगे बढ़ने से पहले यह निर्धारित करने में समय व्यतीत करना चाहिए कि उसे क्या साफ करना है।


इसके अलावा, स्वचालित मेमोरी प्रबंधन में मेमोरी की आवश्यकता अधिक होती है। यह इस तथ्य के कारण है कि एक कचरा संग्रहकर्ता हमारे लिए मेमोरी डीलोकेशन करता है, जो मेमोरी और सीपीयू चक्र दोनों का उपभोग करता है। परिणामस्वरूप, स्वचालित स्मृति प्रबंधन अनुप्रयोग प्रदर्शन को ख़राब कर सकता है, विशेष रूप से सीमित संसाधनों वाले बड़े अनुप्रयोगों में।


प्रमुख टेकअवे हैं:

  • डेवलपर्स को मैन्युअल रूप से मेमोरी जारी करने की आवश्यकता को समाप्त करता है।
  • बिना लटके संदर्भ या मेमोरी लीक के कुशल मेमोरी सुरक्षा प्रदान करता है।
  • सरल और सीधा कोड।
  • तेजी से विकास चक्र।
  • स्मृति प्रबंधन पर कम नियंत्रण रखें।
  • विलंबता का कारण बनता है क्योंकि यह मेमोरी और सीपीयू चक्र दोनों का उपभोग करता है।

रस्ट मेमोरी सुरक्षा की गारंटी कैसे देता है?

कुछ भाषाएं कचरा संग्रहण प्रदान करती हैं, जो उस मेमोरी की तलाश करती है जो प्रोग्राम के चलने के दौरान अब उपयोग में नहीं है; दूसरों को प्रोग्रामर को स्मृति को स्पष्ट रूप से आवंटित करने और जारी करने की आवश्यकता होती है। इन दोनों मॉडलों के फायदे और नुकसान हैं। कचरा संग्रह, हालांकि शायद सबसे व्यापक रूप से उपयोग किया जाता है, में कुछ कमियां हैं; यह संसाधनों और प्रदर्शन की कीमत पर डेवलपर्स के लिए जीवन को आसान बनाता है।


ऐसा कहने के बाद, एक कुशल स्मृति प्रबंधन नियंत्रण देता है, जबकि दूसरा झूलने वाले संदर्भों और मेमोरी लीक को समाप्त करके उच्च सुरक्षा प्रदान करता है। जंग दोनों दुनिया के लाभों को जोड़ती है।


चित्र 2: रस्ट का मेमोरी प्रबंधन पर बेहतर नियंत्रण होता है और स्मृति समस्याओं के बिना उच्च सुरक्षा प्रदान करता है।


रस्ट अन्य दो की तुलना में चीजों के लिए एक अलग दृष्टिकोण लेता है, एक स्वामित्व मॉडल के आधार पर नियमों के एक सेट के साथ जो संकलक स्मृति सुरक्षा सुनिश्चित करने के लिए सत्यापित करता है। यदि इनमें से किसी भी नियम का उल्लंघन किया जाता है तो कार्यक्रम संकलित नहीं होगा। वास्तव में, स्वामित्व रनटाइम कचरा संग्रह को स्मृति सुरक्षा के लिए संकलन-समय जांच के साथ बदल देता है।


स्पष्ट स्मृति प्रबंधन बनाम निहित स्मृति प्रबंधन बनाम जंग का स्वामित्व मॉडल।


स्वामित्व के अभ्यस्त होने में कुछ समय लगता है क्योंकि यह मेरे जैसे कई प्रोग्रामर के लिए एक नई अवधारणा है।

स्वामित्व

इस बिंदु पर, हमें एक बुनियादी समझ है कि मेमोरी में डेटा कैसे संग्रहीत किया जाता है। आइए रस्ट में स्वामित्व को अधिक बारीकी से देखें। जंग की सबसे बड़ी विशिष्ट विशेषता स्वामित्व है, जो संकलन-समय पर स्मृति सुरक्षा सुनिश्चित करती है।


शुरू करने के लिए, आइए "स्वामित्व" को इसके सबसे शाब्दिक अर्थ में परिभाषित करें। स्वामित्व "कुछ" के "मालिक" और "नियंत्रित" कानूनी कब्जे की स्थिति है। इसके साथ ही, हमें यह पहचानना होगा कि मालिक कौन है और मालिक क्या मालिक है और क्या नियंत्रित करता है । जंग में, प्रत्येक मान का एक चर होता है जिसे उसका स्वामी कहा जाता है। सीधे शब्दों में कहें तो, एक वैरिएबल एक मालिक होता है, और एक वैरिएबल का मान वह होता है जो मालिक का मालिक होता है और उसे नियंत्रित करता है।


चित्रा 3: परिवर्तनीय बंधन मालिक और उसके मूल्य/संसाधन को दर्शाता है।


एक स्वामित्व मॉडल के साथ, स्मृति स्वचालित रूप से जारी (मुक्त) हो जाती है, जब इसका स्वामित्व वाला चर दायरे से बाहर हो जाता है। जब मूल्य दायरे से बाहर हो जाते हैं या किसी अन्य कारण से उनका जीवनकाल समाप्त हो जाता है, तो उनके विनाशक कहलाते हैं। एक विनाशक, विशेष रूप से एक स्वचालित विनाशक, एक ऐसा कार्य है जो संदर्भों को हटाकर प्रोग्राम से एक मूल्य के निशान को हटा देता है और स्मृति को मुक्त करता है।

उधार चेकर

जंग उधार चेकर के माध्यम से स्वामित्व को लागू करता है, a स्थिर विश्लेषक . बॉरो चेकर रस्ट कंपाइलर में एक घटक है जो इस बात पर नज़र रखता है कि पूरे कार्यक्रम में डेटा का उपयोग कहाँ किया जाता है, और स्वामित्व नियमों का पालन करके, यह निर्धारित करने में सक्षम है कि डेटा को कहाँ जारी करने की आवश्यकता है। इसके अलावा, बॉरो चेकर यह सुनिश्चित करता है कि डिलीकेटेड मेमोरी को रनटाइम पर कभी भी एक्सेस नहीं किया जा सकता है। यह समवर्ती उत्परिवर्तन (संशोधन) के कारण होने वाली डेटा दौड़ की संभावना को भी समाप्त करता है।

स्वामित्व नियम

जैसा कि पहले कहा गया है, स्वामित्व मॉडल नियमों के एक सेट पर बनाया गया है जिसे स्वामित्व नियम कहा जाता है, और ये नियम अपेक्षाकृत सरल हैं। रस्ट कंपाइलर (rustc) इन नियमों को लागू करता है:

  • रस्ट में, प्रत्येक मान का एक चर होता है जिसे उसका स्वामी कहा जाता है।
  • एक समय में केवल एक ही स्वामी हो सकता है।
  • जब मालिक दायरे से बाहर हो जाता है, तो मान छोड़ दिया जाएगा।


निम्नलिखित स्मृति त्रुटियाँ इन संकलन-समय जाँच स्वामित्व नियमों द्वारा सुरक्षित हैं:

  • डैंगलिंग संदर्भ: यह वह जगह है जहां एक संदर्भ एक स्मृति पते को इंगित करता है जिसमें अब वह डेटा नहीं होता है जिसे सूचक संदर्भित कर रहा था; यह सूचक शून्य या यादृच्छिक डेटा को इंगित करता है।
  • फ्री के बाद उपयोग करें: यह वह जगह है जहां मेमोरी को एक बार मुक्त करने के बाद एक्सेस किया जाता है, जो क्रैश हो सकता है। इस मेमोरी लोकेशन का उपयोग हैकर्स कोड निष्पादित करने के लिए भी कर सकते हैं।
  • डबल फ़्रीज़: यह वह जगह है जहाँ आवंटित मेमोरी को मुक्त किया जाता है, और फिर से मुक्त किया जाता है। इससे प्रोग्राम क्रैश हो सकता है, संभावित रूप से संवेदनशील जानकारी उजागर हो सकती है। यह एक हैकर को उनके द्वारा चुने गए किसी भी कोड को चलाने की अनुमति देता है।
  • सेगमेंटेशन दोष: यह वह जगह है जहां प्रोग्राम मेमोरी तक पहुंचने का प्रयास करता है, इसे एक्सेस करने की अनुमति नहीं है।
  • बफर ओवररन: यह वह जगह है जहां डेटा की मात्रा मेमोरी बफर की स्टोरेज क्षमता से अधिक हो जाती है, जिससे प्रोग्राम क्रैश हो जाता है।


प्रत्येक स्वामित्व नियम के विवरण में जाने से पहले, copy , move , और clone के बीच के अंतर को समझना महत्वपूर्ण है।

प्रतिलिपि

एक निश्चित आकार (विशेष रूप से आदिम प्रकार) के साथ एक प्रकार को स्टैक पर संग्रहीत किया जा सकता है और इसका दायरा समाप्त होने पर पॉप ऑफ किया जा सकता है, और एक नया, स्वतंत्र चर बनाने के लिए जल्दी और आसानी से कॉपी किया जा सकता है यदि कोड के दूसरे भाग में समान मान की आवश्यकता होती है एक अलग दायरा। चूंकि स्टैक मेमोरी की प्रतिलिपि बनाना सस्ता और तेज़ है, निश्चित आकार वाले आदिम प्रकारों को कॉपी सेमेन्टिक्स कहा जाता है। यह सस्ते में एक संपूर्ण प्रतिकृति (एक डुप्लिकेट) बनाता है।


यह ध्यान देने योग्य है कि निश्चित आकार वाले आदिम प्रकार प्रतिलिपि बनाने के लिए प्रतिलिपि विशेषता को लागू करते हैं।


 let x = "hello"; let y = x; println!("{}", x) // hello println!("{}", y) // hello


जंग में, दो प्रकार के तार होते हैं: String (आवंटित ढेर, और बढ़ने योग्य) और &str (निश्चित आकार, और उत्परिवर्तित नहीं किया जा सकता)।


क्योंकि x को स्टैक पर संग्रहीत किया जाता है, y के लिए दूसरी प्रतिलिपि बनाने के लिए इसके मान की प्रतिलिपि बनाना आसान होता है। ढेर पर संग्रहीत मान के लिए यह मामला नहीं है। स्टैक फ्रेम इस तरह दिखता है:


चित्र 4: x और y दोनों का अपना डेटा है।

डुप्लिकेट डेटा प्रोग्राम रनटाइम और मेमोरी खपत को बढ़ाता है। इसलिए, कॉपी करना डेटा के बड़े हिस्से के लिए उपयुक्त नहीं है।

कदम

रस्ट शब्दावली में, "चाल" का अर्थ है कि स्मृति का स्वामित्व किसी अन्य स्वामी को स्थानांतरित कर दिया गया है। ढेर पर संग्रहीत जटिल प्रकारों के मामले पर विचार करें।


 let s1 = String::from("hello"); let s2 = s1;


हम मान सकते हैं कि दूसरी पंक्ति (यानी let s2 = s1; ) s1 में मान की एक प्रति बनाएगी और इसे s2 से बांध देगी। पर ये स्थिति नहीं है।


हुड के नीचे String के साथ क्या हो रहा है यह देखने के लिए नीचे दिए गए एक पर एक नज़र डालें। एक स्ट्रिंग तीन भागों से बनी होती है, जो स्टैक पर संग्रहीत होती है। वास्तविक सामग्री (हैलो, इस मामले में) ढेर पर संग्रहीत हैं।

  • पॉइंटर - उस मेमोरी को इंगित करता है जिसमें स्ट्रिंग की सामग्री होती है।
  • लंबाई - यह कितनी मेमोरी है, बाइट्स में, String की सामग्री वर्तमान में उपयोग कर रही है।
  • क्षमता - यह मेमोरी की कुल मात्रा है, बाइट्स में, जो String को आवंटक से प्राप्त हुई है।


दूसरे शब्दों में कहें तो मेटाडेटा को स्टैक पर रखा जाता है जबकि वास्तविक डेटा को हीप पर रखा जाता है।


चित्रा 5: स्टैक मेटाडेटा रखता है जबकि ढेर वास्तविक सामग्री रखता है।


जब हम s1 से s2 असाइन करते हैं, तो String मेटाडेटा की प्रतिलिपि बनाई जाती है, जिसका अर्थ है कि हम पॉइंटर, लंबाई और स्टैक पर मौजूद क्षमता की प्रतिलिपि बनाते हैं। हम उस डेटा को ढेर पर कॉपी नहीं करते हैं जिसे पॉइंटर संदर्भित करता है। मेमोरी में डेटा का प्रतिनिधित्व नीचे जैसा दिखता है:


चित्र 6: चर s2 को s1 के सूचक, लंबाई और क्षमता की एक प्रति प्राप्त होती है।


यह ध्यान देने योग्य है कि प्रतिनिधित्व नीचे की तरह नहीं दिखता है, जो कि अगर रस्ट ने हीप डेटा की नकल की तो मेमोरी कैसी दिखेगी। यदि जंग ने ऐसा किया है, तो s2 = s1 ऑपरेशन रनटाइम प्रदर्शन के मामले में बेहद धीमा हो सकता है यदि हीप डेटा बड़ा था।


चित्र 7: यदि रस्ट ने हीप डेटा की प्रतिलिपि बनाई है, तो s2 = s1 क्या कर सकता है, इसके लिए एक और संभावना डेटा प्रतिकृति है। हालांकि, रस्ट डिफ़ॉल्ट रूप से कॉपी नहीं करता है।


ध्यान दें कि जब जटिल प्रकार अब दायरे में नहीं होते हैं, तो रस्ट drop फ़ंक्शन को हीप मेमोरी को स्पष्ट रूप से हटाने के लिए कॉल करेगा। हालाँकि, चित्र 6 में दोनों डेटा पॉइंटर्स एक ही स्थान की ओर इशारा कर रहे हैं, जो कि रस्ट के काम करने का तरीका नहीं है। हम जल्द ही विवरण में शामिल होंगे।


जैसा कि पहले कहा गया है, जब हम s1 से s2 असाइन करते हैं, तो चर s2 को s1 के मेटाडेटा (सूचक, लंबाई और क्षमता) की एक प्रति प्राप्त होती है। लेकिन s1 को s2 को सौंपे जाने के बाद क्या होता है? रस्ट अब s1 को मान्य नहीं मानता। हां, तुमने उसे ठीक पढ़ा।


आइए इस बारे में एक पल के लिए let s2 = s1 असाइनमेंट पर विचार करें। विचार करें कि क्या होता है यदि रस्ट इस असाइनमेंट के बाद भी s1 को मान्य मानता है। जब s2 और s1 दायरे से बाहर हो जाते हैं, तो वे दोनों एक ही मेमोरी को मुक्त करने का प्रयास करेंगे। उह-ओह, यह अच्छा नहीं है। इसे डबल फ्री एरर कहा जाता है, और यह मेमोरी सेफ्टी बग्स में से एक है। स्मृति भ्रष्टाचार का परिणाम स्मृति को दो बार मुक्त करने से हो सकता है, जिससे सुरक्षा जोखिम उत्पन्न हो सकता है।


स्मृति सुरक्षा सुनिश्चित करने के लिए, रस्ट को s1 लाइन के बाद अमान्य माना जाता है let s2 = s1 । इसलिए, जब s1 अब दायरे में नहीं है, रस्ट को कुछ भी जारी करने की आवश्यकता नहीं है। जाँच करें कि क्या होता है यदि हम s2 के निर्माण के बाद s1 का उपयोग करने का प्रयास करते हैं।


 let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // Won't compile. We'll get an error.


हमें नीचे की तरह एक त्रुटि मिलेगी क्योंकि जंग आपको अमान्य संदर्भ का उपयोग करने से रोकता है:


 $ cargo run Compiling playground v0.0.1 (/playground) error[E0382]: borrow of moved value: `s1` --> src/main.rs:6:28 | 3 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 4 | let s2 = s1; | -- value moved here 5 | 6 | println!("{}, world!", s1); | ^^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0382`.


चूंकि रस्ट ने s1 की मेमोरी के स्वामित्व को s2 पर ले let s2 = s1 को "स्थानांतरित" कर दिया, इसलिए इसे s1 अमान्य माना गया। यहाँ s1 के अमान्य होने के बाद स्मृति प्रतिनिधित्व है:


चित्र 8: s1 के बाद स्मृति प्रतिनिधित्व को अमान्य कर दिया गया है।


जब केवल s2 वैध रहता है, तो यह अकेले ही मेमोरी को मुक्त कर देगा जब यह दायरे से बाहर हो जाएगा। नतीजतन, जंग में दोहरी मुक्त त्रुटि की संभावना समाप्त हो जाती है। यह तो बहुत ही अच्छी बात है!

क्लोन

यदि हम केवल स्टैक डेटा ही नहीं, बल्कि String के हीप डेटा को गहराई से कॉपी करना चाहते हैं, तो हम clone नामक एक विधि का उपयोग कर सकते हैं। क्लोन विधि का उपयोग करने का एक उदाहरण यहां दिया गया है:


 let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);


क्लोन विधि का उपयोग करते समय, ढेर डेटा s2 में कॉपी हो जाता है। यह पूरी तरह से काम करता है और निम्नलिखित व्यवहार उत्पन्न करता है:


चित्र 9: क्लोन विधि का उपयोग करते समय, हीप डेटा s2 में कॉपी हो जाता है।


क्लोन पद्धति के उपयोग के गंभीर परिणाम होते हैं; यह न केवल डेटा को कॉपी करता है, बल्कि यह दोनों के बीच किसी भी बदलाव को सिंक्रोनाइज़ भी नहीं करता है। सामान्य तौर पर, क्लोनों को सावधानीपूर्वक और परिणामों के बारे में पूरी जागरूकता के साथ नियोजित किया जाना चाहिए।


अब तक, हमें कॉपी, मूव और क्लोन के बीच अंतर करने में सक्षम होना चाहिए। आइए अब प्रत्येक स्वामित्व नियम को अधिक विस्तार से देखें।

स्वामित्व नियम 1

प्रत्येक मान का एक चर होता है जिसे उसका स्वामी कहा जाता है। इसका तात्पर्य है कि सभी मान चर के स्वामित्व में हैं। नीचे दिए गए उदाहरण में, वेरिएबल s हमारे स्ट्रिंग के पॉइंटर का मालिक है, और दूसरी लाइन में, वेरिएबल x का मान 1 है।


 let s = String::from("Rule 1"); let n = 1;

स्वामित्व नियम 2

एक निश्चित समय में किसी मूल्य का केवल एक स्वामी हो सकता है। किसी के पास कई पालतू जानवर हो सकते हैं, लेकिन जब स्वामित्व मॉडल की बात आती है, तो किसी भी समय केवल एक ही मूल्य होता है :-)


फ्रीपिक द्वारा डिज़ाइन किया गया।


आइए प्राइमेटिव का उपयोग करके उदाहरण देखें, जो निश्चित आकार के संकलन समय पर ज्ञात हैं।


 let x = 10; let y = x; let z = x;


हमने 10 लिया है और इसे x को सौंपा है; दूसरे शब्दों में, x के पास 10 है। फिर हम x ले रहे हैं और इसे y को असाइन कर रहे हैं और हम इसे z को भी असाइन कर रहे हैं। हम जानते हैं कि एक निश्चित समय में केवल एक ही स्वामी हो सकता है, लेकिन हमें यहां कोई त्रुटि नहीं मिल रही है। तो यहाँ क्या हो रहा है कि संकलक हर बार x की प्रतियां बना रहा है जब हम इसे एक नए चर के लिए असाइन करते हैं।


इसके लिए स्टैक फ्रेम इस प्रकार होगा: x = 10 , y = 10 और z = 10 । हालांकि, ऐसा प्रतीत नहीं होता है: x = 10 , y = x , और z = x । जैसा कि हम जानते हैं, x इस मान 10 का एकमात्र स्वामी है, और न तो y और न ही z इस मान का स्वामी हो सकता है।


चित्र 10: कंपाइलर ने y और z दोनों के लिए x की प्रतियां बनाईं।


क्योंकि स्टैक मेमोरी की प्रतिलिपि बनाना सस्ता और तेज़ है, एक निश्चित आकार वाले आदिम प्रकारों को कॉपी शब्दार्थ कहा जाता है, जबकि जटिल प्रकार स्वामित्व को स्थानांतरित करते हैं, जैसा कि पहले कहा गया था। इस प्रकार, इस मामले में, संकलक प्रतियां बनाता है।


इस बिंदु पर, का व्यवहार परिवर्तनीय बंधन अन्य प्रोग्रामिंग भाषाओं के समान है। स्वामित्व के नियमों को स्पष्ट करने के लिए, हमें एक जटिल डेटा प्रकार की आवश्यकता है।


आइए ढेर पर संग्रहीत डेटा को देखें और देखें कि रस्ट कैसे समझता है कि इसे कब साफ करना है; इस उपयोग के मामले के लिए स्ट्रिंग प्रकार एक उत्कृष्ट उदाहरण है। हम स्ट्रिंग के स्वामित्व-संबंधी व्यवहार पर ध्यान देंगे; हालाँकि, ये सिद्धांत अन्य जटिल डेटा प्रकारों पर भी लागू होते हैं।


जटिल प्रकार, जैसा कि हम जानते हैं, ढेर पर डेटा का प्रबंधन करता है, और इसकी सामग्री संकलन समय पर अज्ञात होती है। आइए उसी उदाहरण को देखें जो हमने पहले देखा है:


 let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // Won't compile. We'll get an error.



String प्रकार के मामले में, आकार का विस्तार हो सकता है और ढेर पर संग्रहीत किया जा सकता है। इसका मतलब है की:

  • रनटाइम पर, मेमोरी एलोकेटर से मेमोरी का अनुरोध किया जाना चाहिए (चलिए इसे पहला भाग कहते हैं)।
  • जब हम अपने String का उपयोग कर रहे होते हैं, तो हमें इस मेमोरी को वापस आवंटक को वापस (रिलीज़) करने की आवश्यकता होती है (चलिए इसे दूसरा भाग कहते हैं)।


हमने (डेवलपर्स) पहले भाग का ध्यान रखा: जब हम String::from कॉल करते हैं, तो इसका कार्यान्वयन उस मेमोरी का अनुरोध करता है जिसकी उसे आवश्यकता होती है। प्रोग्रामिंग भाषाओं में यह हिस्सा लगभग सामान्य है।


हालांकि, दूसरा भाग अलग है। कचरा संग्रहकर्ता (जीसी) वाली भाषाओं में, जीसी उस मेमोरी को ट्रैक और साफ़ करता है जो अब उपयोग में नहीं है, और हमें इसके बारे में चिंता करने की ज़रूरत नहीं है। कचरा संग्रहकर्ता के बिना भाषाओं में, यह हमारी जिम्मेदारी है कि हम यह पहचानें कि स्मृति की अब आवश्यकता नहीं है और इसे स्पष्ट रूप से जारी करने के लिए कहें। इसे सही ढंग से करना हमेशा एक चुनौतीपूर्ण प्रोग्रामिंग कार्य रहा है:

  • अगर हम भूल गए तो हम याददाश्त बर्बाद कर देंगे।
  • यदि हम इसे बहुत जल्दी करते हैं तो हमारे पास एक अमान्य चर होगा।
  • अगर हम इसे दो बार करते हैं तो हमें एक बग मिलेगा।


रस्ट हमारे जीवन को आसान बनाने के लिए मेमोरी डीलोकेशन को एक नए तरीके से हैंडल करता है: एक बार जब वेरिएबल का मालिक होता है तो मेमोरी अपने आप वापस आ जाती है।


चलो व्यापार पर वापस। जंग में, जटिल प्रकारों के लिए, एक चर के लिए एक मान निर्दिष्ट करने, इसे किसी फ़ंक्शन में पास करने, या किसी फ़ंक्शन से इसे वापस करने जैसे संचालन मान की प्रतिलिपि नहीं बनाते हैं: वे इसे स्थानांतरित करते हैं। इसे सीधे शब्दों में कहें, तो जटिल प्रकार स्वामित्व को स्थानांतरित करते हैं।


जब जटिल प्रकार अब दायरे में नहीं होते हैं, तो रस्ट ड्रॉप फ़ंक्शन को स्पष्ट रूप से हीप मेमोरी को हटाने के लिए कॉल करेगा।


स्वामित्व नियम 3

जब मालिक दायरे से बाहर हो जाता है, तो मान छोड़ दिया जाएगा। पिछले मामले पर फिर से विचार करें:


 let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // Won't compile. The value of s1 has already been dropped.


s1 को s2 को सौंपे जाने के बाद s1 का मान गिर गया है ( let s2 = s1 असाइनमेंट स्टेटमेंट में)। इस प्रकार, इस असाइनमेंट के बाद s1 अब मान्य नहीं है। यहाँ s1 को छोड़ने के बाद मेमोरी का प्रतिनिधित्व है:


चित्र 11: स्मृति निरूपण s1 के बाद हटा दिया गया है।

स्वामित्व कैसे चलता है

जंग कार्यक्रम में स्वामित्व को एक चर से दूसरे चर में स्थानांतरित करने के तीन तरीके हैं:

  1. एक चर के मान को दूसरे चर में निर्दिष्ट करना (इस पर पहले ही चर्चा की जा चुकी है)।
  2. किसी फ़ंक्शन के लिए मान पास करना।
  3. समारोह से लौट रहे हैं।

किसी फ़ंक्शन के लिए मान पास करना

किसी फ़ंक्शन के लिए एक मान पास करना शब्दार्थ है जो एक चर के लिए एक मान निर्दिष्ट करने के समान है। असाइनमेंट की तरह ही, किसी फंक्शन में वेरिएबल को पास करने से वह मूव या कॉपी हो जाता है। इस उदाहरण पर एक नज़र डालें, जो कॉपी और मूव दोनों मामलों को दिखाता है:


 fn main() { let s = String::from("hello"); // s comes into scope move_ownership(s); // s's value moves into the function... // so it's no longer valid from this // point forward let x = 5; // x comes into scope makes_copy(x); // x would move into the function // It follows copy semantics since it's // primitive, so we use x afterward } // Here, x goes out of scope, then s. But because s's value was moved, nothing // special happens. fn move_ownership(some_string: String) { // some_string comes into scope println!("{}", some_string); } // Here, some_string goes out of scope and `drop` is called. // The occupied memory is freed. fn makes_copy(some_integer: i32) { // some_integer comes into scope println!("{}", some_integer); } // Here, some_integer goes out of scope. Nothing special happens.


यदि हमने move_ownership पर कॉल के बाद s का उपयोग करने का प्रयास किया, तो जंग एक संकलन-समय त्रुटि फेंक देगा।

एक समारोह से लौटना

लौटाने वाले मान भी स्वामित्व स्थानांतरित कर सकते हैं। नीचे दिया गया उदाहरण एक फ़ंक्शन दिखाता है जो पिछले उदाहरण के समान एनोटेशन के साथ एक मान लौटाता है।


 fn main() { let s1 = gives_ownership(); // gives_ownership moves its return // value into s1 let s2 = String::from("hello"); // s2 comes into scope let s3 = takes_and_gives_back(s2); // s2 is moved into // takes_and_gives_back, which also // moves its return value into s3 } // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing // happens. s1 goes out of scope and is dropped. fn gives_ownership() -> String { // gives_ownership will move its // return value into the function // that calls it let some_string = String::from("yours"); // some_string comes into scope some_string // some_string is returned and // moves out to the calling // function } // This function takes a String and returns it fn takes_and_gives_back(a_string: String) -> String { // a_string comes into // scope a_string // a_string is returned and moves out to the calling function }


एक चर का स्वामित्व हमेशा एक ही पैटर्न का अनुसरण करता है: एक मान को तब स्थानांतरित किया जाता है जब इसे किसी अन्य चर को सौंपा जाता है । जब तक डेटा का स्वामित्व किसी अन्य चर में स्थानांतरित नहीं किया जाता है, जब एक चर जिसमें ढेर पर डेटा शामिल होता है, गुंजाइश से बाहर हो जाता है, तो मान drop द्वारा साफ हो जाएगा।


उम्मीद है, यह हमें एक बुनियादी समझ देता है कि एक स्वामित्व मॉडल क्या है और यह कैसे प्रभावित करता है कि रस्ट मूल्यों को कैसे संभालता है, जैसे कि उन्हें एक दूसरे को सौंपना और उन्हें कार्यों में और बाहर करना।


पकड़ना। एक बात और…


जंग के स्वामित्व मॉडल, जैसा कि सभी अच्छी चीजों के साथ होता है, में कुछ कमियां होती हैं। एक बार जब हम रस्ट पर काम करना शुरू करते हैं तो हमें कुछ असुविधाओं का तुरंत एहसास होता है। हमने देखा होगा कि स्वामित्व लेना और फिर प्रत्येक फ़ंक्शन के साथ स्वामित्व वापस करना थोड़ा असुविधाजनक होता है।

फ्रीपिक द्वारा डिज़ाइन किया गया।


यह कष्टप्रद है कि यदि हम किसी फ़ंक्शन द्वारा लौटाए गए किसी अन्य डेटा के अलावा, किसी फ़ंक्शन में हम जो कुछ भी पास करते हैं, उसे फिर से उपयोग करना चाहते हैं, तो उसे वापस करना होगा। क्या होगा यदि हम चाहते हैं कि कोई फ़ंक्शन किसी मान का स्वामित्व लिए बिना उसका उपयोग करे?


निम्नलिखित उदाहरण पर विचार करें। नीचे दिए गए कोड के परिणामस्वरूप एक त्रुटि होगी क्योंकि print_vector , v अब main फ़ंक्शन द्वारा उपयोग नहीं किया जा सकता है ( println! )


 fn main() { let v = vec![10,20,30]; print_vector(v); println!("{}", v[0]); // this line gives us an error } fn print_vector(x: Vec<i32>) { println!("Inside print_vector function {:?}",x); }


स्वामित्व को ट्रैक करना काफी आसान लग सकता है, लेकिन जब हम बड़े और जटिल कार्यक्रमों से निपटना शुरू करते हैं तो यह जटिल हो सकता है। इसलिए हमें स्वामित्व को स्थानांतरित किए बिना मूल्यों को स्थानांतरित करने का एक तरीका चाहिए, जहां उधार लेने की अवधारणा चलन में आती है।

उधार

उधार लेना, अपने शाब्दिक अर्थ में, इसे वापस करने के वादे के साथ कुछ प्राप्त करने का संदर्भ देता है। जंग के संदर्भ में, उधार लेना इसके स्वामित्व का दावा किए बिना मूल्य तक पहुंचने का एक तरीका है, क्योंकि इसे किसी बिंदु पर अपने मालिक को वापस करना होगा।


फ्रीपिक द्वारा डिज़ाइन किया गया।


जब हम कोई मूल्य उधार लेते हैं, तो हम उसके मेमोरी पते को & ऑपरेटर के साथ संदर्भित करते हैं। A & को संदर्भ कहा जाता है। संदर्भ स्वयं कुछ खास नहीं हैं - हुड के तहत, वे सिर्फ पते हैं। सी पॉइंटर्स से परिचित लोगों के लिए, एक संदर्भ स्मृति के लिए एक सूचक होता है जिसमें एक मान होता है जो किसी अन्य चर से संबंधित (उर्फ स्वामित्व वाला) होता है। यह ध्यान देने योग्य है कि जंग में एक संदर्भ शून्य नहीं हो सकता है। वास्तव में, एक संदर्भ एक सूचक है ; यह सूचक का सबसे बुनियादी प्रकार है। अधिकांश भाषाओं में केवल एक प्रकार का सूचक होता है, लेकिन रस्ट में केवल एक के बजाय विभिन्न प्रकार के सूचक होते हैं। संकेत और उनके विभिन्न प्रकार एक अलग विषय हैं जिस पर अलग से चर्चा की जाएगी।


इसे सीधे शब्दों में कहें, तो रस्ट कुछ मूल्य के संदर्भ में मूल्य उधार लेने के लिए संदर्भित करता है, जिसे अंततः अपने मालिक को वापस करना होगा।


आइए नीचे एक सरल उदाहरण देखें:


 let x = 5; let y = &x; println!("Value y={}", y); println!("Address of y={:p}", y); println!("Deref of y={}", *y);


उपरोक्त निम्नलिखित आउटपुट उत्पन्न करता है:


 Value y=5 Address of y=0x7fff6c0f131c Deref of y=5


यहां, y चर x के स्वामित्व वाली संख्या उधार लेता है, जबकि x अभी भी मान का स्वामी है। हम y को x का संदर्भ कहते हैं। जब y दायरे से बाहर हो जाता है तो उधार समाप्त हो जाता है, और क्योंकि y के पास मूल्य नहीं है, यह नष्ट नहीं होता है। एक मूल्य उधार लेने के लिए, & ऑपरेटर द्वारा एक संदर्भ लें। p स्वरूपण, {:p} हेक्साडेसिमल के रूप में प्रस्तुत स्मृति स्थान के रूप में आउटपुट।


उपरोक्त कोड में, "*" (अर्थात, एक तारांकन चिह्न) एक डीरेफरेंस ऑपरेटर है जो एक संदर्भ चर पर संचालित होता है। यह डीरेफ्रेंसिंग ऑपरेटर हमें पॉइंटर के मेमोरी एड्रेस में संग्रहीत मूल्य प्राप्त करने की अनुमति देता है।


आइए देखें कि कैसे एक फ़ंक्शन उधार के माध्यम से स्वामित्व लिए बिना किसी मूल्य का उपयोग कर सकता है:


 fn main() { let v = vec![10,20,30]; print_vector(&v); println!("{}", v[0]); // can access v here as references can't move the value } fn print_vector(x: &Vec<i32>) { println!("Inside print_vector function {:?}", x); }


हम स्वामित्व (यानी, पास-बाय-वैल्यू ) को स्थानांतरित करने के बजाय print_vector फ़ंक्शन के लिए एक संदर्भ ( &v ) (उर्फ पास-बाय-रेफरेंस ) पास कर रहे हैं। नतीजतन, मुख्य फ़ंक्शन में print_vector फ़ंक्शन को कॉल करने के बाद, हम v तक पहुंच सकते हैं।

डेरेफरेंस ऑपरेटर के साथ मूल्य के सूचक के बाद

जैसा कि पहले कहा गया है, एक संदर्भ एक प्रकार का सूचक है, और एक सूचक को कहीं और संग्रहीत मूल्य की ओर इशारा करते हुए एक तीर के रूप में माना जा सकता है। नीचे दिए गए उदाहरण पर विचार करें:


 let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y);


उपरोक्त कोड में, हम i32 प्रकार के मान के लिए एक संदर्भ बनाते हैं और फिर डेटा के संदर्भ का पालन करने के लिए dereference ऑपरेटर का उपयोग करते हैं। चर x में i32 प्रकार का मान होता है, 5 । हम y को x के संदर्भ के बराबर सेट करते हैं।


स्टैक मेमोरी इस प्रकार दिखाई देती है:


स्टैक मेमोरी प्रतिनिधित्व।


हम दावा कर सकते हैं कि x 5 के बराबर है। हालाँकि, यदि हम y में मान पर एक अभिकथन करना चाहते हैं, तो हमें उस मान के संदर्भ का पालन करना चाहिए जिसका वह उपयोग कर रहा है *y (इसलिए यहाँ dereference)। एक बार जब हम y को हटा देते हैं, तो हमारे पास उस पूर्णांक मान तक पहुंच होती है, जिसे y इंगित कर रहा है, जिसकी तुलना हम 5 से कर सकते हैं।


अगर हमने लिखने की कोशिश की assert_eq!(5, y); इसके बजाय, हमें यह संकलन त्रुटि मिलेगी:


 error[E0277]: can't compare `{integer}` with `&{integer}` --> src/main.rs:11:5 | 11 | assert_eq!(5, y); | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`


चूंकि वे अलग-अलग प्रकार के होते हैं, इसलिए किसी संख्या और किसी संख्या के संदर्भ की तुलना करने की अनुमति नहीं है। इसलिए, हमें उस मान के संदर्भ का पालन करने के लिए dereference ऑपरेटर का उपयोग करना चाहिए जो वह इंगित कर रहा है।

संदर्भ डिफ़ॉल्ट रूप से अपरिवर्तनीय हैं

चर की तरह, एक संदर्भ डिफ़ॉल्ट रूप से अपरिवर्तनीय है - इसे mut के साथ परिवर्तनशील बनाया जा सकता है, लेकिन केवल तभी जब उसका स्वामी भी परिवर्तनशील हो:


 let mut x = 5; let y = &mut x;


अपरिवर्तनीय संदर्भों को साझा संदर्भ के रूप में भी जाना जाता है, जबकि परिवर्तनीय संदर्भों को अनन्य संदर्भों के रूप में भी जाना जाता है।


नीचे दिए गए मामले पर विचार करें। हम संदर्भों को केवल पढ़ने के लिए पहुंच प्रदान कर रहे हैं क्योंकि हम &mut के बजाय & ऑपरेटर का उपयोग कर रहे हैं। भले ही स्रोत n परिवर्तनशील हो, ref_to_n , और another_ref_to_n नहीं हैं, क्योंकि वे केवल-पढ़ने के लिए n उधार हैं।


 let mut n = 10; let ref_to_n = &n; let another_ref_to_n = &n;


उधार चेकर नीचे त्रुटि देगा:


 error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable --> src/main.rs:4:9 | 3 | let x = 5; | - help: consider changing this to be mutable: `mut x` 4 | let y = &mut x; | ^^^^^^ cannot borrow as mutable


उधार नियम

कोई सवाल कर सकता है कि एक कदम पर हमेशा उधार लेने को प्राथमिकता क्यों नहीं दी जाएगी। अगर ऐसा है, तो रस्ट में मूव सिमेंटिक क्यों होता है, और यह डिफ़ॉल्ट रूप से उधार क्यों नहीं लेता है? कारण यह है कि जंग में एक मूल्य उधार लेना हमेशा संभव नहीं होता है। कुछ मामलों में ही उधार लेने की अनुमति है।


उधार लेने के नियमों का अपना सेट होता है, जिसे उधार लेने वाला चेकर संकलन समय के दौरान सख्ती से लागू करता है। ये नियम डेटा दौड़ को रोकने के लिए बनाए गए थे। वे इस प्रकार हैं:

  1. उधारकर्ता का दायरा मूल मालिक के दायरे से अधिक नहीं रह सकता।
  2. कई अपरिवर्तनीय संदर्भ हो सकते हैं, लेकिन केवल एक परिवर्तनशील संदर्भ हो सकता है।
  3. मालिकों के पास अपरिवर्तनीय या परिवर्तनशील संदर्भ हो सकते हैं, लेकिन एक ही समय में दोनों नहीं।
  4. सभी संदर्भ मान्य होने चाहिए (शून्य नहीं हो सकते)।

संदर्भ स्वामी से अधिक नहीं रहना चाहिए

संदर्भ का दायरा मूल्य के स्वामी के दायरे में होना चाहिए। अन्यथा, संदर्भ एक मुक्त मूल्य का उल्लेख कर सकता है, जिसके परिणामस्वरूप उपयोग-बाद-मुक्त त्रुटि हो सकती है।


 let x; { let y = 0; x = &y; } println!("{}", x);


मालिक y के दायरे से बाहर हो जाने के बाद उपरोक्त प्रोग्राम x को डीरेफरेंस करने का प्रयास करता है। जंग इस उपयोग के बाद मुक्त त्रुटि को रोकता है।

कई अपरिवर्तनीय संदर्भ, लेकिन केवल एक परिवर्तनीय संदर्भ की अनुमति है

हमारे पास एक समय में डेटा के एक विशेष टुकड़े के लिए कई अपरिवर्तनीय संदर्भ (उर्फ साझा संदर्भ) हो सकते हैं, लेकिन एक पल में केवल एक परिवर्तनीय संदर्भ (उर्फ अनन्य संदर्भ) की अनुमति है। यह नियम डेटा दौड़ को खत्म करने के लिए मौजूद है। जब दो संदर्भ एक ही समय में एक ही स्मृति स्थान की ओर इशारा करते हैं, उनमें से कम से कम एक लिख रहा है, और उनके कार्यों को सिंक्रनाइज़ नहीं किया जाता है, इसे डेटा दौड़ के रूप में जाना जाता है।


हमारे पास जितने चाहें उतने अपरिवर्तनीय संदर्भ हो सकते हैं क्योंकि वे डेटा को नहीं बदलते हैं। दूसरी ओर, उधार लेना हमें संकलन समय पर डेटा दौड़ की संभावना को रोकने के लिए एक समय में केवल एक परिवर्तनीय संदर्भ ( &mut ) रखने के लिए प्रतिबंधित करता है।


आइए इसे देखें:


 fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2); }


उपरोक्त कोड जो दो परस्पर संदर्भ ( r1 और r2 ) से s बनाने का प्रयास विफल हो जाएगा:


 error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:6:14 | 5 | let r1 = &mut s; | ------ first mutable borrow occurs here 6 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 7 | 8 | println!("{}, {}", r1, r2); | -- first borrow later used here


अंतिम शब्द

उम्मीद है, यह स्वामित्व और उधार की अवधारणाओं को स्पष्ट करता है। मैंने उधार चेकर, स्वामित्व और उधार की रीढ़ की हड्डी पर भी संक्षेप में बात की। जैसा कि मैंने शुरुआत में उल्लेख किया है, स्वामित्व एक नया विचार है जिसे पहले समझना मुश्किल हो सकता है, यहां तक कि अनुभवी डेवलपर्स के लिए भी, लेकिन जितना अधिक आप इस पर काम करते हैं उतना आसान और आसान हो जाता है। यह रस्ट में स्मृति सुरक्षा को कैसे लागू किया जाता है, इसका एक संक्षिप्त विवरण है। मैंने अवधारणाओं को समझने के लिए पर्याप्त जानकारी प्रदान करते हुए इस पोस्ट को यथासंभव आसान बनाने का प्रयास किया। रस्ट की स्वामित्व विशेषता के बारे में अधिक जानकारी के लिए, उनके ऑनलाइन दस्तावेज़ देखें।


जब प्रदर्शन मायने रखता है तो जंग एक बढ़िया विकल्प है और यह कई अन्य भाषाओं को परेशान करने वाले दर्द बिंदुओं को हल करता है, जिसके परिणामस्वरूप एक महत्वपूर्ण कदम आगे बढ़ने की अवस्था में होता है। लगातार छठे वर्ष, रस्ट स्टैक ओवरफ्लो की सबसे पसंदीदा भाषा रही है, जिसका अर्थ है कि बहुत से लोग जिन्हें इसका उपयोग करने का मौका मिला है, उन्हें इससे प्यार हो गया है। जंग समुदाय का विकास जारी है।


रस्ट सर्वे 2021 के परिणाम के अनुसार : वर्ष 2021 निस्संदेह रस्ट के इतिहास में सबसे महत्वपूर्ण में से एक था। इसने रस्ट फाउंडेशन की स्थापना, 2021 संस्करण और पहले से कहीं अधिक बड़े समुदाय को देखा। जैसे ही हम भविष्य में आगे बढ़ते हैं, जंग एक मजबूत सड़क पर दिखाई देती है।


हैप्पी लर्निंग!


फ्रीपिक द्वारा डिज़ाइन किया गया।