मैं उसी मूल्य को उसी संरचना में मान और संदर्भ क्यों नहीं संग्रहीत कर सकता हूं?


222

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

struct Thing {
    count: u32,
}

struct Combined<'a>(Thing, &'a u32);

fn make_combined<'a>() -> Combined<'a> {
    let thing = Thing { count: 42 };

    Combined(thing, &thing.count)
}

कभी-कभी, मेरे पास एक मूल्य होता है और मैं उस मूल्य और उस मूल्य के संदर्भ को उसी संरचना में संग्रहीत करना चाहता हूं:

struct Combined<'a>(Thing, &'a Thing);

fn make_combined<'a>() -> Combined<'a> {
    let thing = Thing::new();

    Combined(thing, &thing)
}

कभी-कभी, मैं मूल्य का संदर्भ भी नहीं ले रहा हूं और मुझे वही त्रुटि मिलती है:

struct Combined<'a>(Parent, Child<'a>);

fn make_combined<'a>() -> Combined<'a> {
    let parent = Parent::new();
    let child = parent.child();

    Combined(parent, child)
}

इन मामलों में से प्रत्येक में, मुझे एक त्रुटि मिलती है कि मूल्यों में से एक "लंबे समय तक नहीं रहता है"। इस त्रुटि का मतलब क्या है?


1
बाद के उदाहरण के लिए, की एक परिभाषा Parentऔर Childमदद कर सकती है ...
Matthieu M.

1
@MatthieuM। मैंने उस पर बहस की, लेकिन दो जुड़े सवालों के आधार पर इसके खिलाफ फैसला किया। उन प्रश्नों में से न तो प्रश्न की संरचना या विधि की परिभाषा को देखा , इसलिए मैंने सोचा कि यह नकल करना सबसे अच्छा होगा कि लोग इस प्रश्न को अपनी स्थिति से आसानी से मिला सकते हैं। नोट मुझे लगता है कि ऐसा जवाब में विधि हस्ताक्षर दिखा।
शेमपस्टर

जवाबों:


245

आइए इस पर एक सरल कार्यान्वयन देखें :

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

ठोस जीवन के parent1 से 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 तालिका में जुड़ती है वह यह बताने का एक सामान्य तरीका है कि किसी दिए गए मूल्य को स्थानांतरित न करने की गारंटी है।

यह सभी देखें:


1
क्या ऐसा कुछ ( is.gd/wl2IAt ) मुहावरेदार माना जाता है? यानी, कच्चे डेटा के बजाय तरीकों के माध्यम से डेटा को उजागर करना।
पीटर हॉल

2
@PeterHall यकीन है, यह सिर्फ मतलब है कि Combinedमालिक है Childजो मालिक है Parent। आपके पास वास्तविक प्रकारों के आधार पर यह समझ में नहीं आता है या हो सकता है। अपने स्वयं के आंतरिक डेटा के संदर्भ में लौटना बहुत विशिष्ट है।
शमपास्टर

ढेर समस्या का समाधान क्या है?
डेरेकड्रीरी

@derekdreery शायद आप अपनी टिप्पणी पर विस्तार कर सकते हैं? क्यों पूरा पैरा के बारे में बात कर रही है owning_ref टोकरा अपर्याप्त?
शेमपस्टर

1
@FynnBecker अभी भी एक संदर्भ और उस संदर्भ के लिए एक मूल्य संग्रहीत करना असंभव है । Pinएक संरचना का एक तरीका है, जिसमें एक सेल्फ रेफरेंशियल पॉइंटर वाले सेफ्टी की सुरक्षा को जाना जाता है । रॉस्ट 1.0 के बाद से इसी उद्देश्य के लिए एक कच्चे सूचक का उपयोग करने की क्षमता मौजूद है।
शेमपस्टर

4

थोड़ा अलग मुद्दा जो बहुत समान संकलक संदेशों का कारण बनता है वह एक स्पष्ट संदर्भ को संग्रहीत करने के बजाय ऑब्जेक्ट लाइफटाइम निर्भरता है। इसका एक उदाहरण ssh2 पुस्तकालय है। परीक्षण परियोजना की तुलना में कुछ बड़ा विकसित करते समय, उपयोगकर्ता से कार्यान्वयन विवरण छिपाते हुए, एक दूसरे के साथ उस सत्र से प्राप्त करने Sessionऔर Channelउस सत्र से प्राप्त करने की कोशिश करना आकर्षक है । हालाँकि, ध्यान दें कि Channelपरिभाषा का 'sessजीवनकाल अपने प्रकार के एनोटेशन में है, जबकि Sessionऐसा नहीं है।

यह जीवनकाल से संबंधित समान संकलक त्रुटियों का कारण बनता है।

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

किराये के टोकरे को बदल देता है या अन्य जवाब से खुद के मालिक को इस मुद्दे के लिए भी समाधान है। आइए मालिक के बारे में विचार करें OwningHandle। इस सटीक उद्देश्य के लिए विशेष वस्तु है :। अंतर्निहित ऑब्जेक्ट से बचने के लिए, हम इसे एक का उपयोग करके ढेर पर आवंटित करते हैं Box, जो हमें निम्नलिखित संभव समाधान प्रदान करता है:

use ssh2::{Channel, Error, Session};
use std::net::TcpStream;

use owning_ref::OwningHandle;

struct DeviceSSHConnection {
    tcp: TcpStream,
    channel: OwningHandle<Box<Session>, Box<Channel<'static>>>,
}

impl DeviceSSHConnection {
    fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
        use std::net::TcpStream;
        let mut session = Session::new().unwrap();
        let mut tcp = TcpStream::connect(targ).unwrap();

        session.handshake(&tcp).unwrap();
        session.set_timeout(5000);
        session.userauth_password(c_user, c_pass).unwrap();

        let mut sess = Box::new(session);
        let mut oref = OwningHandle::new_with_fn(
            sess,
            unsafe { |x| Box::new((*x).channel_session().unwrap()) },
        );

        oref.shell().unwrap();
        let ret = DeviceSSHConnection {
            tcp: tcp,
            channel: oref,
        };
        ret
    }
}

इस कोड का परिणाम यह है कि हम Sessionअब और उपयोग नहीं कर सकते हैं, लेकिन इसके साथ संग्रहीत किया जाता है Channelजिसके साथ हम उपयोग करेंगे। क्योंकि OwningHandleऑब्जेक्ट डीरेफरेंस करते हैं Box, जो डीरेफरेंस करते हैं Channel, जब इसे किसी स्ट्रक्चर में स्टोर करते हैं, तो हम इसे इस तरह से नाम देते हैं। नोट: यह सिर्फ मेरी समझ है। मुझे संदेह है कि यह सही नहीं हो सकता है, क्योंकि यह अनिश्चितता की चर्चा केOwningHandle काफी करीब प्रतीत होता है ।

यहाँ एक जिज्ञासु विवरण यह है कि Sessionतार्किक रूप से जैसा संबंध होता है, TcpStreamवैसा Channelही होता है Session, फिर भी इसका स्वामित्व नहीं लिया जाता है और ऐसा करने के आस-पास किसी प्रकार की कोई टिप्पणी नहीं होती है। इसके बजाय, उपयोगकर्ता को इस पर ध्यान देना है, क्योंकि हैंडशेक विधि का प्रलेखन कहता है:

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

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

इसलिए TcpStreamउपयोग के साथ , कोड की शुद्धता सुनिश्चित करने के लिए प्रोग्रामर पर पूरी तरह से निर्भर है। OwningHandleध्यान के साथ , जहां "खतरनाक जादू" होता है, का ध्यान unsafe {}ब्लॉक का उपयोग करके खींचा जाता है ।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.