आइए इस पर एक सरल कार्यान्वयन देखें :
struct Parent {
count: u32,
}
struct Child<'a> {
parent: &'a Parent,
}
struct Combined<'a> {
parent: Parent,
child: Child<'a>,
}
impl<'a> Combined<'a> {
fn new() -> Self {
let parent = Parent { count: 42 };
let child = Child { parent: &parent };
Combined { parent, child }
}
}
fn main() {}
यह त्रुटि के साथ विफल हो जाएगा:
error[E0515]: cannot return value referencing local variable `parent`
--> src/main.rs:19:9
|
17 | let child = Child { parent: &parent };
| ------- `parent` is borrowed here
18 |
19 | Combined { parent, child }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `parent` because it is borrowed
--> src/main.rs:19:20
|
14 | impl<'a> Combined<'a> {
| -- lifetime `'a` defined here
...
17 | let child = Child { parent: &parent };
| ------- borrow of `parent` occurs here
18 |
19 | Combined { parent, child }
| -----------^^^^^^---------
| | |
| | move out of `parent` occurs here
| returning this value requires that `parent` is borrowed for `'a`
इस त्रुटि को पूरी तरह से समझने के लिए, आपको यह सोचना होगा कि मूल्यों को स्मृति में कैसे दर्शाया जाता है और जब आप
उन मूल्यों को स्थानांतरित करते हैं तो क्या होता है। आइए Combined::new
कुछ काल्पनिक स्मृति पतों से व्याख्या करें जो यह दर्शाते हैं कि मान कहाँ स्थित हैं:
let parent = Parent { count: 42 };
// `parent` lives at address 0x1000 and takes up 4 bytes
// The value of `parent` is 42
let child = Child { parent: &parent };
// `child` lives at address 0x1010 and takes up 4 bytes
// The value of `child` is 0x1000
Combined { parent, child }
// The return value lives at address 0x2000 and takes up 8 bytes
// `parent` is moved to 0x2000
// `child` is ... ?
क्या होना चाहिए child
? यदि मूल्य को ऐसे ही स्थानांतरित कर दिया parent
गया था, तो यह स्मृति को संदर्भित करेगा कि अब इसमें मान्य मूल्य होने की गारंटी नहीं है। कोड के किसी अन्य टुकड़े को स्मृति पते 0x1000 पर मूल्यों को संग्रहीत करने की अनुमति है। उस स्मृति तक पहुँचना यह मानते हुए कि पूर्णांक क्रैश और / या सुरक्षा बग हो सकता है, और रुस्त को रोकने वाली त्रुटियों की मुख्य श्रेणियों में से एक है।
यह वास्तव में समस्या है जो जीवनकाल को रोकती है। एक जीवनकाल मेटाडेटा का एक सा है जो आपको और संकलक को यह जानने की अनुमति देता है कि इसके वर्तमान मेमोरी स्थान पर कब तक मान मान्य होगा । यह एक महत्वपूर्ण अंतर है, क्योंकि यह एक आम गलती है रस्ट नवागंतुक। जंग जीवनकाल नहीं हैंकिसी वस्तु के निर्मित होने और उसके नष्ट होने के बीच होती है!
एक सादृश्य के रूप में, इस तरह से सोचें: किसी व्यक्ति के जीवन के दौरान, वे कई अलग-अलग स्थानों में निवास करेंगे, प्रत्येक एक अलग पते के साथ। एक जंग का जीवनकाल उस पते से संबंधित होता है, जिस पर आप वर्तमान में निवास करते हैं , न कि इस बारे में कि आप भविष्य में कब मरेंगे (हालाँकि मरने से आपका पता भी बदल जाता है)। हर बार जब आप इसे स्थानांतरित करते हैं तो यह प्रासंगिक होता है क्योंकि आपका पता अब मान्य नहीं है।
यह भी ध्यान रखना महत्वपूर्ण है कि जीवन काल आपके कोड को नहीं बदलता है; आपका कोड जीवन काल को नियंत्रित करता है, आपके जीवन काल कोड को नियंत्रित नहीं करते हैं। पैथी कहावत है "जीवनकाल वर्णनात्मक हैं, प्रिस्क्रिप्टिव नहीं"।
आइए Combined::new
कुछ लाइन नंबरों के साथ एनोटेट करें जिसका उपयोग हम जीवनकाल को उजागर करने के लिए करेंगे:
{ // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
ठोस जीवन के parent
1 से 4 के लिए है, समावेशी (जो मैं के रूप में प्रतिनिधित्व करेंगे [1,4]
)। का ठोस जीवनकाल child
है [2,4]
, और वापसी मूल्य का ठोस जीवनकाल है [4,5]
। यह संभव है कि ठोस जीवन रेखाएँ जो शून्य से शुरू हों - जो किसी कार्य के लिए एक पैरामीटर के जीवनकाल का प्रतिनिधित्व करेंगी या ऐसा कुछ जो ब्लॉक के बाहर मौजूद थीं।
ध्यान दें कि child
स्वयं का जीवनकाल है [2,4]
, लेकिन यह जीवनकाल के साथ एक मूल्य को संदर्भित करता है[1,4]
। यह तब तक ठीक है जब तक संदर्भित मूल्य, संदर्भित मूल्य से पहले अमान्य हो जाता है। समस्या तब होती है जब हम child
ब्लॉक से लौटने की कोशिश करते हैं । यह अपनी प्राकृतिक लंबाई से परे जीवन भर "अति-विस्तार" करेगा।
इस नए ज्ञान को पहले दो उदाहरणों की व्याख्या करनी चाहिए। तीसरे को लागू करने की आवश्यकता है Parent::child
। संभावना है, यह कुछ इस तरह दिखेगा:
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
यह स्पष्ट सामान्य आजीवन मापदंडों को लिखने से बचने के लिए आजीवन अतिरिक्त का उपयोग करता है । यह इसके बराबर है:
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
दोनों मामलों में, विधि कहती है कि एक Child
संरचना वापस आ जाएगी जिसे कंक्रीट के जीवनकाल के साथ मानकीकृत किया गया है
self
। दूसरे तरीके से कहा गया है, Child
उदाहरण में Parent
इसे बनाए जाने का संदर्भ है , और इस प्रकार वह Parent
उदाहरण से अधिक समय तक नहीं रह सकता है
।
इससे हमें यह भी पता चलता है कि हमारे निर्माण कार्य में कुछ गड़बड़ है:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
हालाँकि आपको इस रूप को एक अलग रूप में देखने की अधिक संभावना है:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
दोनों ही मामलों में, एक तर्क के माध्यम से जीवनकाल पैरामीटर प्रदान नहीं किया जा रहा है। इसका मतलब यह है कि जीवनकाल जिसे Combined
कुछ के साथ सीमित किया जाएगा, वह किसी भी चीज से विवश नहीं है - यह वह हो सकता है जो कॉल करने वाला चाहता है। यह निरर्थक है, क्योंकि कॉल करने वाला 'static
जीवनकाल को निर्दिष्ट कर सकता है और उस स्थिति को पूरा करने का कोई तरीका नहीं है।
मैं इसे कैसे ठीक करूं?
सबसे आसान और सबसे अनुशंसित समाधान इन वस्तुओं को एक ही संरचना में एक साथ रखने का प्रयास नहीं करना है। ऐसा करने से, आपकी संरचना घोंसले के शिकार आपके कोड के जीवनकाल की नकल करेगी। उन प्रकारों को रखें, जो डेटा को एक संरचना में एक साथ रखते हैं और फिर ऐसी विधियाँ प्रदान करते हैं जो आपको संदर्भ या आवश्यकता वाले ऑब्जेक्ट प्राप्त करने की अनुमति देती हैं।
एक विशेष मामला है जहां आजीवन ट्रैकिंग अति उत्साही है: जब आपके पास ढेर पर कुछ रखा जाता है। यह तब होता है जब आप Box<T>
उदाहरण के लिए, का उपयोग करते हैं
। इस स्थिति में, ले जाया गया संरचना ढेर में एक सूचक है। इंगित-पर-मूल्य स्थिर रहेगा, लेकिन सूचक का पता स्वयं चल जाएगा। व्यवहार में, यह कोई फर्क नहीं पड़ता, क्योंकि आप हमेशा सूचक का पालन करते हैं।
किराए पर लेने की टोकरा (अब बनाई नहीं रखी है या समर्थित) या owning_ref टोकरा इस मामले का प्रतिनिधित्व करने के तरीके हैं, लेकिन वे अपेक्षा करते हैं कि आधार पते के लिए कदम कभी नहीं । यह म्यूटिंग वैक्टर को बाहर निकालता है, जो एक वास्तविक आवंटन और ढेर-आवंटित मूल्यों का एक कदम हो सकता है।
किराये के साथ हल की गई समस्याओं के उदाहरण:
अन्य मामलों में, आप कुछ प्रकार की संदर्भ-गणना करने की इच्छा कर सकते हैं, जैसे कि उपयोग करके Rc
या Arc
।
अधिक जानकारी
parent
संरचना में जाने के बाद , कंपाइलर संरचना में एक नया संदर्भ प्राप्त करने parent
और उसे असाइन करने child
में सक्षम क्यों नहीं है ?
हालांकि ऐसा करना सैद्धांतिक रूप से संभव है, ऐसा करने से जटिलता और ओवरहेड की एक बड़ी मात्रा का परिचय होगा। हर बार जब वस्तु को स्थानांतरित किया जाता है, तो संकलक को संदर्भ को "ठीक करने" के लिए कोड डालने की आवश्यकता होगी। इसका मतलब यह होगा कि एक संरचना की नकल करना अब बहुत सस्ता ऑपरेशन नहीं है जो बस कुछ बिट्स को घुमाता है। इसका अर्थ यह भी हो सकता है कि इस तरह का कोड महंगा है, यह इस बात पर निर्भर करता है कि काल्पनिक आशावादी कितना अच्छा होगा:
let a = Object::new();
let b = a;
let c = b;
हर कदम के लिए ऐसा होने के लिए मजबूर करने के बजाय , प्रोग्रामर को यह चुनने के लिए मिलता है कि यह ऐसे तरीकों का निर्माण करके होगा जो आपको कॉल करने पर ही उपयुक्त संदर्भ लेंगे।
स्वयं के संदर्भ के साथ एक प्रकार
एक विशिष्ट मामला है जहां आप स्वयं के संदर्भ में एक प्रकार बना सकते हैं। Option
हालांकि आपको इसे दो चरणों में बनाने के लिए कुछ का उपयोग करने की आवश्यकता है :
#[derive(Debug)]
struct WhatAboutThis<'a> {
name: String,
nickname: Option<&'a str>,
}
fn main() {
let mut tricky = WhatAboutThis {
name: "Annabelle".to_string(),
nickname: None,
};
tricky.nickname = Some(&tricky.name[..4]);
println!("{:?}", tricky);
}
यह काम करता है, कुछ अर्थों में, लेकिन बनाया मूल्य अत्यधिक प्रतिबंधित है - इसे कभी भी स्थानांतरित नहीं किया जा सकता है। विशेष रूप से, इसका मतलब यह है कि इसे किसी फ़ंक्शन से नहीं लौटाया जा सकता है या किसी भी चीज़ से पारित नहीं किया जा सकता है। एक निर्माण कार्य ऊपर के रूप में जीवनकाल के साथ एक ही समस्या दिखाता है:
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
किस बारे में Pin
?
Pin
, रस्ट 1.33 में स्थिर, मॉड्यूल प्रलेखन में यह है :
इस तरह के परिदृश्य का एक प्रमुख उदाहरण स्वयं-संदर्भात्मक संरचनाओं का निर्माण करना होगा, क्योंकि एक ऑब्जेक्ट को अपने साथ ले जाने से उन्हें अमान्य कर दिया जाएगा, जो अपरिभाषित व्यवहार का कारण बन सकता है।
यह ध्यान रखना महत्वपूर्ण है कि "सेल्फ रेफ़रेंशियल" का अर्थ किसी संदर्भ का उपयोग करना नहीं है । वास्तव में, विशेष रूप से एक स्व-संदर्भात्मक संरचना का उदाहरण कहता है (जोर मेरा):
हम सामान्य संदर्भ के साथ इस बारे में संकलक को सूचित नहीं कर सकते, क्योंकि इस पैटर्न को सामान्य उधार नियमों के साथ वर्णित नहीं किया जा सकता है। इसके बजाय हम एक कच्चे पॉइंटर का उपयोग करते हैं , हालांकि एक जिसे शून्य नहीं जाना जाता है, क्योंकि हम जानते हैं कि यह स्ट्रिंग पर इंगित कर रहा है।
रॉस्ट 1.0 के बाद से इस व्यवहार के लिए एक कच्चे सूचक का उपयोग करने की क्षमता मौजूद है। दरअसल, हूडिंग के तहत मालिक-रेफ और किराये कच्चे पॉइंटर्स का उपयोग करते हैं।
केवल एक चीज Pin
तालिका में जुड़ती है वह यह बताने का एक सामान्य तरीका है कि किसी दिए गए मूल्य को स्थानांतरित न करने की गारंटी है।
यह सभी देखें:
Parent
औरChild
मदद कर सकती है ...