रस्ट का स्वामित्व और उधारी भ्रमित करने वाली हो सकती है यदि हम यह नहीं समझ पाते हैं कि वास्तव में क्या हो रहा है। यह विशेष रूप से सच है जब पहले से सीखी गई प्रोग्रामिंग शैली को एक नए प्रतिमान में लागू किया जाता है; हम इसे एक प्रतिमान बदलाव कहते हैं। स्वामित्व एक नया विचार है, फिर भी पहली बार में समझना मुश्किल है, लेकिन जितना अधिक हम इस पर काम करते हैं, यह आसान हो जाता है।
इससे पहले कि हम रस्ट के स्वामित्व और उधार के बारे में आगे बढ़ें, आइए पहले समझते हैं कि "मेमोरी सेफ्टी" और "मेमोरी लीक" क्या हैं और प्रोग्रामिंग भाषाएं उनसे कैसे निपटती हैं।
मेमोरी सेफ्टी एक सॉफ्टवेयर एप्लिकेशन की स्थिति को संदर्भित करता है जहां मेमोरी पॉइंटर्स या संदर्भ हमेशा वैध मेमोरी को संदर्भित करते हैं। चूंकि स्मृति भ्रष्टाचार एक संभावना है, इसलिए प्रोग्राम के व्यवहार के बारे में बहुत कम गारंटी है यदि यह स्मृति सुरक्षित नहीं है। सीधे शब्दों में कहें, यदि कोई प्रोग्राम वास्तव में मेमोरी सुरक्षित नहीं है, तो इसकी कार्यक्षमता के बारे में कुछ आश्वासन हैं। मेमोरी-असुरक्षित प्रोग्राम के साथ काम करते समय, एक दुर्भावनापूर्ण पार्टी किसी अन्य की मशीन पर रहस्यों को पढ़ने या मनमाने कोड को निष्पादित करने के लिए दोष का उपयोग करने में सक्षम होती है।
आइए एक स्यूडोकोड का उपयोग करके देखें कि वैध मेमोरी क्या है।
// 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
एक लटकता हुआ संदर्भ एक सूचक है जो एक स्मृति स्थान को इंगित करता है जो किसी और को दिया गया है या जारी किया गया है (मुक्त)। यदि कोई प्रोग्राम (उर्फ प्रक्रिया ) स्मृति को संदर्भित करता है जिसे जारी किया गया है या मिटा दिया गया है, तो यह क्रैश हो सकता है या गैर-निर्धारक परिणाम उत्पन्न कर सकता है।
ऐसा कहने के बाद, स्मृति असुरक्षितता कुछ प्रोग्रामिंग भाषाओं की एक संपत्ति है जो प्रोग्रामर को अमान्य डेटा से निपटने की अनुमति देती है। नतीजतन, स्मृति असुरक्षितता ने कई तरह की समस्याएं पेश कीं जो निम्नलिखित प्रमुख सुरक्षा कमजोरियों का कारण बन सकती हैं:
स्मृति असुरक्षितता के कारण होने वाली भेद्यताएं कई अन्य गंभीर सुरक्षा खतरों की जड़ में हैं। दुर्भाग्य से, इन कमजोरियों को उजागर करना डेवलपर्स के लिए बेहद चुनौतीपूर्ण हो सकता है।
यह समझना महत्वपूर्ण है कि मेमोरी लीक क्या है और इसके परिणाम क्या हैं।
एक स्मृति रिसाव स्मृति खपत का एक अनजाने रूप है जिससे डेवलपर ढेर स्मृति के आवंटित ब्लॉक को मुक्त करने में विफल रहता है जब इसकी आवश्यकता नहीं होती है। यह स्मृति सुरक्षा के बिल्कुल विपरीत है। विभिन्न मेमोरी प्रकारों के बारे में बाद में, लेकिन अभी के लिए, बस यह जान लें कि एक स्टैक निश्चित-लंबाई वाले चर को संकलित समय पर संग्रहीत करता है, जबकि चर का आकार जो बाद में रनटाइम पर बदल सकता है, उसे ढेर पर रखा जाना चाहिए।
हीप मेमोरी आवंटन की तुलना में, स्टैक मेमोरी आवंटन को सुरक्षित माना जाता है क्योंकि मेमोरी स्वचालित रूप से रिलीज़ हो जाती है जब यह अब प्रासंगिक या आवश्यक नहीं है, या तो प्रोग्रामर द्वारा या प्रोग्राम-रनटाइम द्वारा।
हालांकि, जब प्रोग्रामर ढेर पर मेमोरी उत्पन्न करते हैं और कचरा संग्रहकर्ता (सी और सी ++ के मामले में) की अनुपस्थिति में इसे हटाने में विफल होते हैं, तो स्मृति रिसाव विकसित होता है। साथ ही, अगर हम उस स्मृति को हटाए बिना स्मृति के एक हिस्से के सभी संदर्भ खो देते हैं, तो हमारे पास स्मृति रिसाव है। हमारा प्रोग्राम उस मेमोरी का स्वामी बना रहेगा, लेकिन उसके पास इसे फिर से उपयोग करने का कोई तरीका नहीं है।
थोड़ी सी मेमोरी लीक कोई समस्या नहीं है, लेकिन अगर कोई प्रोग्राम बड़ी मात्रा में मेमोरी आवंटित करता है और इसे कभी नहीं हटाता है, तो प्रोग्राम की मेमोरी फ़ुटप्रिंट बढ़ती रहेगी, जिसके परिणामस्वरूप सेवा से इनकार किया जा सकता है।
जब कोई प्रोग्राम बाहर निकलता है, तो ऑपरेटिंग सिस्टम उसके पास मौजूद सभी मेमोरी को तुरंत रिकवर कर लेता है। नतीजतन, एक स्मृति रिसाव केवल एक प्रोग्राम को प्रभावित करता है जब वह चल रहा हो; कार्यक्रम समाप्त होने के बाद इसका कोई प्रभाव नहीं पड़ता है।
आइए मेमोरी लीक के प्रमुख परिणामों पर चलते हैं।
मेमोरी लीक उपलब्ध मेमोरी (हीप मेमोरी) की मात्रा को कम करके कंप्यूटर के प्रदर्शन को कम करता है। यह अंततः पूरे या सिस्टम के एक हिस्से को सही ढंग से काम करना बंद कर देता है या गंभीर रूप से धीमा कर देता है। क्रैश आमतौर पर मेमोरी लीक से जुड़े होते हैं।
मेमोरी लीक को रोकने के तरीके का पता लगाने के लिए हमारा दृष्टिकोण उस प्रोग्रामिंग भाषा के आधार पर अलग-अलग होगा जिसका हम उपयोग कर रहे हैं। मेमोरी लीक एक छोटी और लगभग "अनदेखी समस्या" के रूप में शुरू हो सकता है, लेकिन वे बहुत तेज़ी से बढ़ सकते हैं और सिस्टम को प्रभावित कर सकते हैं। जहां भी संभव हो, हमें उनकी तलाश में रहना चाहिए और उन्हें बढ़ने के बजाय उन्हें सुधारने के लिए कार्रवाई करनी चाहिए।
मेमोरी लीक और मेमोरी असुरक्षित दो प्रकार के मुद्दे हैं जिन पर रोकथाम और उपचार के मामले में सबसे अधिक ध्यान दिया गया है। यह ध्यान रखना महत्वपूर्ण है कि एक को ठीक करने से दूसरे को स्वचालित रूप से ठीक नहीं किया जाता है।
इससे पहले कि हम आगे बढ़ें, विभिन्न प्रकार की मेमोरी को समझना महत्वपूर्ण है जो हमारा कोड रनटाइम पर उपयोग करेगा।
स्मृति दो प्रकार की होती है, जो इस प्रकार हैं, और इन स्मृतियों की संरचना अलग-अलग होती है।
प्रोसेसर रजिस्टर
स्थिर
ढेर
ढेर
प्रोसेसर रजिस्टर और स्थिर मेमोरी प्रकार दोनों इस पोस्ट के दायरे से बाहर हैं।
स्टैक डेटा को प्राप्त होने के क्रम में संग्रहीत करता है और इसे उल्टे क्रम में हटा देता है। आइटम को स्टैक से लास्ट इन, फर्स्ट आउट (LIFO) ऑर्डर में एक्सेस किया जा सकता है। स्टैक पर डेटा जोड़ने को "पुशिंग" कहा जाता है और स्टैक से डेटा को हटाने को "पॉपिंग" कहा जाता है।
स्टैक पर संग्रहीत सभी डेटा का एक ज्ञात, निश्चित आकार होना चाहिए। संकलन समय पर अज्ञात आकार वाले डेटा या बाद में बदल सकने वाले आकार को इसके बजाय हीप पर संग्रहीत किया जाना चाहिए।
डेवलपर्स के रूप में, हमें स्टैक मेमोरी आवंटन और डीलोकेशन के बारे में चिंता करने की ज़रूरत नहीं है; स्टैक मेमोरी का आवंटन और डीलोकेशन कंपाइलर द्वारा "स्वचालित रूप से किया जाता है"। इसका तात्पर्य यह है कि जब स्टैक पर डेटा अब प्रासंगिक (दायरे से बाहर) नहीं है, तो यह हमारे हस्तक्षेप की आवश्यकता के बिना स्वचालित रूप से हटा दिया जाता है।
इस प्रकार की मेमोरी आवंटन को अस्थायी मेमोरी आवंटन के रूप में भी जाना जाता है, क्योंकि जैसे ही फ़ंक्शन अपना निष्पादन समाप्त करता है, उस फ़ंक्शन से संबंधित सभी डेटा "स्वचालित रूप से" स्टैक से बाहर निकल जाता है।
जंग में सभी आदिम प्रकार ढेर पर रहते हैं। संख्याएं, वर्ण, स्लाइस, बूलियन, निश्चित आकार के सरणी, आदिम युक्त टुपल्स, और फ़ंक्शन पॉइंटर्स जैसे प्रकार सभी स्टैक पर बैठ सकते हैं।
स्टैक के विपरीत, जब हम ढेर पर डेटा डालते हैं, तो हम एक निश्चित मात्रा में स्थान का अनुरोध करते हैं। मेमोरी आवंटक ढेर में एक बड़ी पर्याप्त खाली जगह का पता लगाता है, इसे उपयोग के रूप में चिह्नित करता है, और उस स्थान के पते का संदर्भ देता है। इसे आवंटन कहा जाता है।
ढेर पर आवंटन ढेर को धक्का देने से धीमा है क्योंकि आवंटक को नया डेटा डालने के लिए खाली स्थान की तलाश नहीं करनी पड़ती है। इसके अलावा, क्योंकि हमें ढेर पर डेटा प्राप्त करने के लिए एक पॉइंटर का पालन करना चाहिए, यह स्टैक पर डेटा तक पहुंचने से धीमा है। स्टैक के विपरीत, जिसे संकलन समय पर आवंटित और हटा दिया जाता है, हीप मेमोरी को प्रोग्राम के निर्देशों के निष्पादन के दौरान आवंटित और हटा दिया जाता है।
कुछ प्रोग्रामिंग भाषाओं में, हीप मेमोरी आवंटित करने के लिए, हम कीवर्ड new
का उपयोग करते हैं। यह new
कीवर्ड (उर्फ ऑपरेटर ) ढेर पर स्मृति आवंटन के अनुरोध को दर्शाता है। यदि ढेर पर पर्याप्त स्मृति उपलब्ध है, तो new
ऑपरेटर स्मृति को प्रारंभ करता है और उस नई आवंटित स्मृति का अद्वितीय पता देता है।
यह ध्यान देने योग्य है कि हीप मेमोरी को प्रोग्रामर या रनटाइम द्वारा "स्पष्ट रूप से" हटा दिया जाता है।
जब स्मृति प्रबंधन की बात आती है, विशेष रूप से मेमोरी को ढेर करने की, तो हम अपनी प्रोग्रामिंग भाषाओं को निम्नलिखित विशेषताओं के लिए पसंद करेंगे:
प्रोग्रामिंग भाषाओं द्वारा विभिन्न तरीकों से मेमोरी सुरक्षा सुनिश्चित की जाती है:
क्षेत्र-आधारित स्मृति प्रबंधन और रैखिक प्रकार प्रणाली दोनों इस पद के दायरे से बाहर हैं।
प्रोग्रामर को स्पष्ट स्मृति प्रबंधन का उपयोग करते समय आवंटित स्मृति को "मैन्युअल रूप से" जारी या मिटा देना चाहिए। एक "डीलोकेशन" ऑपरेटर (उदाहरण के लिए, सी में delete
) स्पष्ट मेमोरी डीलोकेशन वाली भाषाओं में मौजूद है।
सी और सी ++ जैसी सिस्टम भाषाओं में कचरा संग्रह बहुत महंगा है, इसलिए स्पष्ट स्मृति आवंटन मौजूद है।
स्मृति को मुक्त करने की जिम्मेदारी प्रोग्रामर पर छोड़ने से प्रोग्रामर को चर के जीवन चक्र पर पूर्ण नियंत्रण देने का लाभ मिलता है। हालाँकि, यदि डीललोकेशन ऑपरेटरों का गलत तरीके से उपयोग किया जाता है, तो निष्पादन के दौरान एक सॉफ़्टवेयर त्रुटि हो सकती है। वास्तव में, यह मैनुअल आवंटन और जारी करने की प्रक्रिया त्रुटियों से ग्रस्त है। कुछ सामान्य कोडिंग त्रुटियों में शामिल हैं:
इसके बावजूद, हमने कचरा संग्रहण पर मैनुअल मेमोरी प्रबंधन को प्राथमिकता दी क्योंकि यह हमें अधिक नियंत्रण देता है और बेहतर प्रदर्शन प्रदान करता है। ध्यान दें कि किसी भी सिस्टम प्रोग्रामिंग भाषा का लक्ष्य जितना संभव हो "धातु के करीब" प्राप्त करना है। दूसरे शब्दों में, वे ट्रेडऑफ़ में सुविधा सुविधाओं पर बेहतर प्रदर्शन का पक्ष लेते हैं।
यह पूरी तरह से हमारी (डेवलपर्स) जिम्मेदारी है कि हम यह सुनिश्चित करें कि हमारे द्वारा मुक्त किए गए मूल्य का कोई संकेतक कभी भी उपयोग नहीं किया जाता है।
हाल के दिनों में, इन त्रुटियों से बचने के लिए कई सिद्ध पैटर्न रहे हैं, लेकिन यह सब कठोर कोड अनुशासन बनाए रखने के लिए उबलता है, जिसके लिए लगातार सही मेमोरी प्रबंधन पद्धति को लागू करने की आवश्यकता होती है।
प्रमुख टेकअवे हैं:
जावा सहित सभी आधुनिक प्रोग्रामिंग भाषाओं की स्वचालित स्मृति प्रबंधन एक अनिवार्य विशेषता बन गई है।
स्वचालित मेमोरी डीलोकेशन के मामले में, कचरा संग्रहकर्ता स्वचालित मेमोरी मैनेजर के रूप में कार्य करता है। ये कचरा संग्रहकर्ता समय-समय पर मेमोरी के ढेर और रीसायकल के माध्यम से जाते हैं जिनका उपयोग नहीं किया जा रहा है। वे हमारी ओर से स्मृति के आवंटन और रिलीज का प्रबंधन करते हैं। इसलिए हमें स्मृति प्रबंधन कार्यों को करने के लिए कोड लिखने की आवश्यकता नहीं है। यह बहुत अच्छा है क्योंकि कचरा संग्रहकर्ता हमें स्मृति प्रबंधन की जिम्मेदारी से मुक्त करते हैं। एक और फायदा यह है कि यह विकास के समय को कम करता है।
दूसरी ओर, कचरा संग्रह में कई कमियां हैं। कचरा संग्रहण के दौरान, कार्यक्रम को रुकना चाहिए और आगे बढ़ने से पहले यह निर्धारित करने में समय व्यतीत करना चाहिए कि उसे क्या साफ करना है।
इसके अलावा, स्वचालित मेमोरी प्रबंधन में मेमोरी की आवश्यकता अधिक होती है। यह इस तथ्य के कारण है कि एक कचरा संग्रहकर्ता हमारे लिए मेमोरी डीलोकेशन करता है, जो मेमोरी और सीपीयू चक्र दोनों का उपभोग करता है। परिणामस्वरूप, स्वचालित स्मृति प्रबंधन अनुप्रयोग प्रदर्शन को ख़राब कर सकता है, विशेष रूप से सीमित संसाधनों वाले बड़े अनुप्रयोगों में।
प्रमुख टेकअवे हैं:
कुछ भाषाएं कचरा संग्रहण प्रदान करती हैं, जो उस मेमोरी की तलाश करती है जो प्रोग्राम के चलने के दौरान अब उपयोग में नहीं है; दूसरों को प्रोग्रामर को स्मृति को स्पष्ट रूप से आवंटित करने और जारी करने की आवश्यकता होती है। इन दोनों मॉडलों के फायदे और नुकसान हैं। कचरा संग्रह, हालांकि शायद सबसे व्यापक रूप से उपयोग किया जाता है, में कुछ कमियां हैं; यह संसाधनों और प्रदर्शन की कीमत पर डेवलपर्स के लिए जीवन को आसान बनाता है।
ऐसा कहने के बाद, एक कुशल स्मृति प्रबंधन नियंत्रण देता है, जबकि दूसरा झूलने वाले संदर्भों और मेमोरी लीक को समाप्त करके उच्च सुरक्षा प्रदान करता है। जंग दोनों दुनिया के लाभों को जोड़ती है।
रस्ट अन्य दो की तुलना में चीजों के लिए एक अलग दृष्टिकोण लेता है, एक स्वामित्व मॉडल के आधार पर नियमों के एक सेट के साथ जो संकलक स्मृति सुरक्षा सुनिश्चित करने के लिए सत्यापित करता है। यदि इनमें से किसी भी नियम का उल्लंघन किया जाता है तो कार्यक्रम संकलित नहीं होगा। वास्तव में, स्वामित्व रनटाइम कचरा संग्रह को स्मृति सुरक्षा के लिए संकलन-समय जांच के साथ बदल देता है।
स्वामित्व के अभ्यस्त होने में कुछ समय लगता है क्योंकि यह मेरे जैसे कई प्रोग्रामर के लिए एक नई अवधारणा है।
इस बिंदु पर, हमें एक बुनियादी समझ है कि मेमोरी में डेटा कैसे संग्रहीत किया जाता है। आइए रस्ट में स्वामित्व को अधिक बारीकी से देखें। जंग की सबसे बड़ी विशिष्ट विशेषता स्वामित्व है, जो संकलन-समय पर स्मृति सुरक्षा सुनिश्चित करती है।
शुरू करने के लिए, आइए "स्वामित्व" को इसके सबसे शाब्दिक अर्थ में परिभाषित करें। स्वामित्व "कुछ" के "मालिक" और "नियंत्रित" कानूनी कब्जे की स्थिति है। इसके साथ ही, हमें यह पहचानना होगा कि मालिक कौन है और मालिक क्या मालिक है और क्या नियंत्रित करता है । जंग में, प्रत्येक मान का एक चर होता है जिसे उसका स्वामी कहा जाता है। सीधे शब्दों में कहें तो, एक वैरिएबल एक मालिक होता है, और एक वैरिएबल का मान वह होता है जो मालिक का मालिक होता है और उसे नियंत्रित करता है।
एक स्वामित्व मॉडल के साथ, स्मृति स्वचालित रूप से जारी (मुक्त) हो जाती है, जब इसका स्वामित्व वाला चर दायरे से बाहर हो जाता है। जब मूल्य दायरे से बाहर हो जाते हैं या किसी अन्य कारण से उनका जीवनकाल समाप्त हो जाता है, तो उनके विनाशक कहलाते हैं। एक विनाशक, विशेष रूप से एक स्वचालित विनाशक, एक ऐसा कार्य है जो संदर्भों को हटाकर प्रोग्राम से एक मूल्य के निशान को हटा देता है और स्मृति को मुक्त करता है।
जंग उधार चेकर के माध्यम से स्वामित्व को लागू करता है, a
जैसा कि पहले कहा गया है, स्वामित्व मॉडल नियमों के एक सेट पर बनाया गया है जिसे स्वामित्व नियम कहा जाता है, और ये नियम अपेक्षाकृत सरल हैं। रस्ट कंपाइलर (rustc) इन नियमों को लागू करता है:
निम्नलिखित स्मृति त्रुटियाँ इन संकलन-समय जाँच स्वामित्व नियमों द्वारा सुरक्षित हैं:
प्रत्येक स्वामित्व नियम के विवरण में जाने से पहले, copy , move , और clone के बीच के अंतर को समझना महत्वपूर्ण है।
एक निश्चित आकार (विशेष रूप से आदिम प्रकार) के साथ एक प्रकार को स्टैक पर संग्रहीत किया जा सकता है और इसका दायरा समाप्त होने पर पॉप ऑफ किया जा सकता है, और एक नया, स्वतंत्र चर बनाने के लिए जल्दी और आसानी से कॉपी किया जा सकता है यदि कोड के दूसरे भाग में समान मान की आवश्यकता होती है एक अलग दायरा। चूंकि स्टैक मेमोरी की प्रतिलिपि बनाना सस्ता और तेज़ है, निश्चित आकार वाले आदिम प्रकारों को कॉपी सेमेन्टिक्स कहा जाता है। यह सस्ते में एक संपूर्ण प्रतिकृति (एक डुप्लिकेट) बनाता है।
यह ध्यान देने योग्य है कि निश्चित आकार वाले आदिम प्रकार प्रतिलिपि बनाने के लिए प्रतिलिपि विशेषता को लागू करते हैं।
let x = "hello"; let y = x; println!("{}", x) // hello println!("{}", y) // hello
जंग में, दो प्रकार के तार होते हैं:
String
(आवंटित ढेर, और बढ़ने योग्य) और&str
(निश्चित आकार, और उत्परिवर्तित नहीं किया जा सकता)।
क्योंकि x
को स्टैक पर संग्रहीत किया जाता है, y
के लिए दूसरी प्रतिलिपि बनाने के लिए इसके मान की प्रतिलिपि बनाना आसान होता है। ढेर पर संग्रहीत मान के लिए यह मामला नहीं है। स्टैक फ्रेम इस तरह दिखता है:
डुप्लिकेट डेटा प्रोग्राम रनटाइम और मेमोरी खपत को बढ़ाता है। इसलिए, कॉपी करना डेटा के बड़े हिस्से के लिए उपयुक्त नहीं है।
रस्ट शब्दावली में, "चाल" का अर्थ है कि स्मृति का स्वामित्व किसी अन्य स्वामी को स्थानांतरित कर दिया गया है। ढेर पर संग्रहीत जटिल प्रकारों के मामले पर विचार करें।
let s1 = String::from("hello"); let s2 = s1;
हम मान सकते हैं कि दूसरी पंक्ति (यानी let s2 = s1;
) s1
में मान की एक प्रति बनाएगी और इसे s2
से बांध देगी। पर ये स्थिति नहीं है।
हुड के नीचे String
के साथ क्या हो रहा है यह देखने के लिए नीचे दिए गए एक पर एक नज़र डालें। एक स्ट्रिंग तीन भागों से बनी होती है, जो स्टैक पर संग्रहीत होती है। वास्तविक सामग्री (हैलो, इस मामले में) ढेर पर संग्रहीत हैं।
String
की सामग्री वर्तमान में उपयोग कर रही है।String
को आवंटक से प्राप्त हुई है।
दूसरे शब्दों में कहें तो मेटाडेटा को स्टैक पर रखा जाता है जबकि वास्तविक डेटा को हीप पर रखा जाता है।
जब हम s1
से s2
असाइन करते हैं, तो String
मेटाडेटा की प्रतिलिपि बनाई जाती है, जिसका अर्थ है कि हम पॉइंटर, लंबाई और स्टैक पर मौजूद क्षमता की प्रतिलिपि बनाते हैं। हम उस डेटा को ढेर पर कॉपी नहीं करते हैं जिसे पॉइंटर संदर्भित करता है। मेमोरी में डेटा का प्रतिनिधित्व नीचे जैसा दिखता है:
यह ध्यान देने योग्य है कि प्रतिनिधित्व नीचे की तरह नहीं दिखता है, जो कि अगर रस्ट ने हीप डेटा की नकल की तो मेमोरी कैसी दिखेगी। यदि जंग ने ऐसा किया है, तो 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 के अमान्य होने के बाद स्मृति प्रतिनिधित्व है:
जब केवल s2
वैध रहता है, तो यह अकेले ही मेमोरी को मुक्त कर देगा जब यह दायरे से बाहर हो जाएगा। नतीजतन, जंग में दोहरी मुक्त त्रुटि की संभावना समाप्त हो जाती है। यह तो बहुत ही अच्छी बात है!
यदि हम केवल स्टैक डेटा ही नहीं, बल्कि String
के हीप डेटा को गहराई से कॉपी करना चाहते हैं, तो हम clone
नामक एक विधि का उपयोग कर सकते हैं। क्लोन विधि का उपयोग करने का एक उदाहरण यहां दिया गया है:
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);
क्लोन विधि का उपयोग करते समय, ढेर डेटा s2 में कॉपी हो जाता है। यह पूरी तरह से काम करता है और निम्नलिखित व्यवहार उत्पन्न करता है:
क्लोन पद्धति के उपयोग के गंभीर परिणाम होते हैं; यह न केवल डेटा को कॉपी करता है, बल्कि यह दोनों के बीच किसी भी बदलाव को सिंक्रोनाइज़ भी नहीं करता है। सामान्य तौर पर, क्लोनों को सावधानीपूर्वक और परिणामों के बारे में पूरी जागरूकता के साथ नियोजित किया जाना चाहिए।
अब तक, हमें कॉपी, मूव और क्लोन के बीच अंतर करने में सक्षम होना चाहिए। आइए अब प्रत्येक स्वामित्व नियम को अधिक विस्तार से देखें।
प्रत्येक मान का एक चर होता है जिसे उसका स्वामी कहा जाता है। इसका तात्पर्य है कि सभी मान चर के स्वामित्व में हैं। नीचे दिए गए उदाहरण में, वेरिएबल s
हमारे स्ट्रिंग के पॉइंटर का मालिक है, और दूसरी लाइन में, वेरिएबल x
का मान 1 है।
let s = String::from("Rule 1"); let n = 1;
एक निश्चित समय में किसी मूल्य का केवल एक स्वामी हो सकता है। किसी के पास कई पालतू जानवर हो सकते हैं, लेकिन जब स्वामित्व मॉडल की बात आती है, तो किसी भी समय केवल एक ही मूल्य होता है :-)
आइए प्राइमेटिव का उपयोग करके उदाहरण देखें, जो निश्चित आकार के संकलन समय पर ज्ञात हैं।
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
इस मान का स्वामी हो सकता है।
क्योंकि स्टैक मेमोरी की प्रतिलिपि बनाना सस्ता और तेज़ है, एक निश्चित आकार वाले आदिम प्रकारों को कॉपी शब्दार्थ कहा जाता है, जबकि जटिल प्रकार स्वामित्व को स्थानांतरित करते हैं, जैसा कि पहले कहा गया था। इस प्रकार, इस मामले में, संकलक प्रतियां बनाता है।
इस बिंदु पर, का व्यवहार
आइए ढेर पर संग्रहीत डेटा को देखें और देखें कि रस्ट कैसे समझता है कि इसे कब साफ करना है; इस उपयोग के मामले के लिए स्ट्रिंग प्रकार एक उत्कृष्ट उदाहरण है। हम स्ट्रिंग के स्वामित्व-संबंधी व्यवहार पर ध्यान देंगे; हालाँकि, ये सिद्धांत अन्य जटिल डेटा प्रकारों पर भी लागू होते हैं।
जटिल प्रकार, जैसा कि हम जानते हैं, ढेर पर डेटा का प्रबंधन करता है, और इसकी सामग्री संकलन समय पर अज्ञात होती है। आइए उसी उदाहरण को देखें जो हमने पहले देखा है:
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // Won't compile. We'll get an error.
String
प्रकार के मामले में, आकार का विस्तार हो सकता है और ढेर पर संग्रहीत किया जा सकता है। इसका मतलब है की:
- रनटाइम पर, मेमोरी एलोकेटर से मेमोरी का अनुरोध किया जाना चाहिए (चलिए इसे पहला भाग कहते हैं)।
- जब हम अपने
String
का उपयोग कर रहे होते हैं, तो हमें इस मेमोरी को वापस आवंटक को वापस (रिलीज़) करने की आवश्यकता होती है (चलिए इसे दूसरा भाग कहते हैं)।
हमने (डेवलपर्स) पहले भाग का ध्यान रखा: जब हम
String::from
कॉल करते हैं, तो इसका कार्यान्वयन उस मेमोरी का अनुरोध करता है जिसकी उसे आवश्यकता होती है। प्रोग्रामिंग भाषाओं में यह हिस्सा लगभग सामान्य है।
हालांकि, दूसरा भाग अलग है। कचरा संग्रहकर्ता (जीसी) वाली भाषाओं में, जीसी उस मेमोरी को ट्रैक और साफ़ करता है जो अब उपयोग में नहीं है, और हमें इसके बारे में चिंता करने की ज़रूरत नहीं है। कचरा संग्रहकर्ता के बिना भाषाओं में, यह हमारी जिम्मेदारी है कि हम यह पहचानें कि स्मृति की अब आवश्यकता नहीं है और इसे स्पष्ट रूप से जारी करने के लिए कहें। इसे सही ढंग से करना हमेशा एक चुनौतीपूर्ण प्रोग्रामिंग कार्य रहा है:
- अगर हम भूल गए तो हम याददाश्त बर्बाद कर देंगे।
- यदि हम इसे बहुत जल्दी करते हैं तो हमारे पास एक अमान्य चर होगा।
- अगर हम इसे दो बार करते हैं तो हमें एक बग मिलेगा।
रस्ट हमारे जीवन को आसान बनाने के लिए मेमोरी डीलोकेशन को एक नए तरीके से हैंडल करता है: एक बार जब वेरिएबल का मालिक होता है तो मेमोरी अपने आप वापस आ जाती है।
चलो व्यापार पर वापस। जंग में, जटिल प्रकारों के लिए, एक चर के लिए एक मान निर्दिष्ट करने, इसे किसी फ़ंक्शन में पास करने, या किसी फ़ंक्शन से इसे वापस करने जैसे संचालन मान की प्रतिलिपि नहीं बनाते हैं: वे इसे स्थानांतरित करते हैं। इसे सीधे शब्दों में कहें, तो जटिल प्रकार स्वामित्व को स्थानांतरित करते हैं।
जब जटिल प्रकार अब दायरे में नहीं होते हैं, तो रस्ट ड्रॉप फ़ंक्शन को स्पष्ट रूप से हीप मेमोरी को हटाने के लिए कॉल करेगा।
जब मालिक दायरे से बाहर हो जाता है, तो मान छोड़ दिया जाएगा। पिछले मामले पर फिर से विचार करें:
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 को छोड़ने के बाद मेमोरी का प्रतिनिधित्व है:
जंग कार्यक्रम में स्वामित्व को एक चर से दूसरे चर में स्थानांतरित करने के तीन तरीके हैं:
किसी फ़ंक्शन के लिए एक मान पास करना शब्दार्थ है जो एक चर के लिए एक मान निर्दिष्ट करने के समान है। असाइनमेंट की तरह ही, किसी फंक्शन में वेरिएबल को पास करने से वह मूव या कॉपी हो जाता है। इस उदाहरण पर एक नज़र डालें, जो कॉपी और मूव दोनों मामलों को दिखाता है:
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
कोई सवाल कर सकता है कि एक कदम पर हमेशा उधार लेने को प्राथमिकता क्यों नहीं दी जाएगी। अगर ऐसा है, तो रस्ट में मूव सिमेंटिक क्यों होता है, और यह डिफ़ॉल्ट रूप से उधार क्यों नहीं लेता है? कारण यह है कि जंग में एक मूल्य उधार लेना हमेशा संभव नहीं होता है। कुछ मामलों में ही उधार लेने की अनुमति है।
उधार लेने के नियमों का अपना सेट होता है, जिसे उधार लेने वाला चेकर संकलन समय के दौरान सख्ती से लागू करता है। ये नियम डेटा दौड़ को रोकने के लिए बनाए गए थे। वे इस प्रकार हैं:
संदर्भ का दायरा मूल्य के स्वामी के दायरे में होना चाहिए। अन्यथा, संदर्भ एक मुक्त मूल्य का उल्लेख कर सकता है, जिसके परिणामस्वरूप उपयोग-बाद-मुक्त त्रुटि हो सकती है।
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 संस्करण और पहले से कहीं अधिक बड़े समुदाय को देखा। जैसे ही हम भविष्य में आगे बढ़ते हैं, जंग एक मजबूत सड़क पर दिखाई देती है।
हैप्पी लर्निंग!