क्या एक प्रकार को केवल चल और प्रतिलिपि बनाने योग्य नहीं बनाया जा सकता है?


96

संपादक का ध्यान : यह प्रश्न रस्ट १.० से पहले पूछा गया था और प्रश्न में कुछ कथन आवश्यक रूप से रस्ट १.० में सत्य नहीं हैं। दोनों संस्करणों को संबोधित करने के लिए कुछ उत्तर अपडेट किए गए हैं।

मेरे पास यह संरचना है

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
}

यदि मैं इसे किसी फ़ंक्शन में पास करता हूं, तो यह अंतर्निहित रूप से कॉपी किया जाता है। अब, कभी-कभी मैं पढ़ता हूं कि कुछ मूल्य प्रतिलिपि योग्य नहीं हैं और इसलिए उन्हें स्थानांतरित करना होगा।

क्या इस संरचना को Tripletगैर-प्रतिलिपि बनाना संभव होगा ? उदाहरण के लिए, क्या ऐसा गुण लागू करना संभव होगा जो Tripletगैर-प्रतिलिपि योग्य हो और इसलिए "चल" हो?

मैंने कहीं पढ़ा है कि किसी को उन Cloneचीजों को कॉपी करने के लिए विशेषता को लागू करना पड़ता है जो कि अनुमानित रूप से कॉपी करने योग्य नहीं हैं, लेकिन मैंने कभी भी दूसरे तरीके के बारे में नहीं पढ़ा, जो कि कुछ ऐसा है जो अनुमानित रूप से कॉपी करने योग्य है और इसे गैर-कॉपी करने योग्य बनाता है ताकि यह बदले में चले।

क्या इससे भी कोई मतलब है?


1
paulkoerbitz.de/posts/…क्यों बनाम बनाम में अच्छी व्याख्याएँ ।
सीन पेरी

जवाबों:


164

प्रस्तावना : यह उत्तर ऑप्ट-इन अंतर्निहित लक्षणों से पहले लिखा गया था - विशेष रूप से Copyपहलुओं को लागू किया गया। मैंने उन खंडों को इंगित करने के लिए ब्लॉक उद्धरण का उपयोग किया है जो केवल पुरानी योजना पर लागू होते हैं (एक है जो प्रश्न पूछे जाने पर लागू होता है)।


पुराना : मूल प्रश्न का उत्तर देने के लिए, आप एक मार्कर फ़ील्ड जोड़ सकते हैं जिसमें NoCopyमान रखा जाता है । उदाहरण के लिए

struct Triplet {
    one: int,
    two: int,
    three: int,
    _marker: NoCopy
}

आप इसे एक विध्वंसक ( Dropविशेषता को लागू करने के माध्यम से ) भी कर सकते हैं , लेकिन यदि विध्वंसक कुछ भी नहीं कर रहा है, तो मार्कर प्रकारों का उपयोग करना पसंद किया जाता है।

प्रकार अब डिफ़ॉल्ट रूप से चलते हैं, जब आप एक नए प्रकार को परिभाषित करते हैं तो यह Copyतब तक लागू नहीं होता है जब तक कि आप इसे स्पष्ट रूप से अपने प्रकार के लिए लागू नहीं करते हैं:

struct Triplet {
    one: i32,
    two: i32,
    three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move

कार्यान्वयन केवल तभी मौजूद हो सकता है जब हर प्रकार नए में निहित हो structया enumवह स्वयं हो Copy। यदि नहीं, तो संकलक एक त्रुटि संदेश मुद्रित करेगा। यह केवल तभी मौजूद हो सकता है यदि प्रकार में कार्यान्वयन नहीं है Drop


आपके द्वारा पूछे गए प्रश्न का उत्तर देने के लिए ... "चाल और प्रतिलिपि के साथ क्या हो रहा है?":

सबसे पहले मैं दो अलग-अलग "प्रतियों" को परिभाषित करूंगा:

  • एक बाइट कॉपी , जो केवल एक वस्तु बाइट-बाइट कॉपी कर रही है, न कि निम्न संकेत, जैसे कि यदि आपके पास है (&usize, u64), तो यह 64-बिट कंप्यूटर पर 16 बाइट्स है, और एक उथली प्रति उन 16 बाइट्स ले जा रही है और उनकी नकल कर रही है स्मृति के कुछ अन्य 16-बाइट चंक में मूल्य, के दूसरे छोर पर स्पर्श किए बिना । यानी यह कॉलिंग के बराबर है ।usize&memcpy
  • एक सिमेंटिक कॉपी , एक नया (कुछ) स्वतंत्र उदाहरण बनाने के लिए एक मूल्य को डुप्लिकेट करता है जिसे सुरक्षित रूप से पुराने के लिए अलग से उपयोग किया जा सकता है। उदाहरण के लिए, एक सम्‍मिलित प्रति Rc<T>सम्‍मिलित करने के लिए केवल संदर्भ गणना बढ़ाना, और एक सम्‍मिलित प्रति सम्‍मिलित Vec<T>करना एक नया आबंटन बनाना, और फिर पुराने से नए में प्रत्‍येक संचित तत्‍व को शाब्दिक रूप से कॉपी करना। ये गहरी प्रतियां हो सकती हैं (उदाहरण के लिए Vec<T>) या उथले (जैसे Rc<T>संग्रहीत को स्पर्श नहीं करता है T), Cloneशिथिल रूप से परिभाषित किया गया है कि Tएक के अंदर से प्रकार के मूल्य को शब्दार्थ रूप से कॉपी करने के लिए आवश्यक काम की सबसे छोटी राशि &Tहै T

जंग सी की तरह है, मूल्य के हर उप-मूल्य का उपयोग एक बाइट कॉपी है:

let x: T = ...;
let y: T = x; // byte copy

fn foo(z: T) -> T {
    return z // byte copy
}

foo(y) // byte copy

वे बाइट प्रतियाँ हैं कि नहीं या नहीं Tचलती है या "अंतर्निहित नकल " है। (स्पष्ट होने के लिए, वे रन-टाइम पर वस्तुतः बाइट-बाय-बाइट प्रतियां नहीं हैं: कोड का व्यवहार संरक्षित होने पर कंपाइलर कॉपियों का अनुकूलन करने के लिए स्वतंत्र है।)

हालाँकि, बाइट प्रतियों के साथ एक बुनियादी समस्या है: आप स्मृति में डुप्लिकेट मूल्यों के साथ समाप्त होते हैं, जो कि विनाशकारी होने पर बहुत खराब हो सकते हैं, जैसे।

{
    let v: Vec<u8> = vec![1, 2, 3];
    let w: Vec<u8> = v;
} // destructors run here

अगर wसिर्फ एक सादे बाइट की प्रति थीv तो एक ही आवंटन पर इंगित करने वाले दो वैक्टर होंगे, दोनों विध्वंसक के साथ जो इसे मुक्त करते हैं ... जिससे दोहरे मुक्त होते हैं , जो एक समस्या है। एनबी। यह बिल्कुल ठीक हो सकता है अगर हम का एक अर्थ प्रतिलिपि किया था, vमें w, तब से wअपनी स्वतंत्र होगा Vec<u8>और विनाशकर्ता एक दूसरे को को कुचल रही नहीं किया जाएगा।

यहाँ कुछ संभावित सुधार हैं:

  • प्रोग्रामर को इसे संभालने दें, जैसे C. (C में कोई विध्वंसक नहीं है, इसलिए यह उतना बुरा नहीं है ... आप इसके बजाय सिर्फ मेमोरी लीक से बचे रहें।: P)
  • अंतर्निहित रूप से एक सिमेंटिक कॉपी करें, ताकि wउसका स्वयं का आवंटन हो, जैसे कि C ++ अपने कॉपी कंस्ट्रक्टर्स के साथ।
  • स्वामित्व मूल्य के हस्तांतरण के रूप में उपयोग करता है, ताकि v इसका उपयोग नहीं किया जा सके और इसका विध्वंसक रन न हो।

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

let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value

ऐसे प्रकार जिनके विनाशकारी होते हैं हैं, जिनका उपयोग तब किया जाना चाहिए जब मूल्य (बाइट की नकल करते समय उर्फ), क्योंकि उनके पास कुछ संसाधन का प्रबंधन / स्वामित्व होता है (जैसे एक मेमोरी आवंटन, या फ़ाइल हैंडल) और इसकी बहुत संभावना नहीं है कि एक बाइट प्रतिलिपि इसे सही ढंग से डुप्लिकेट करेगी स्वामित्व।

"ठीक है ... एक अंतर्निहित प्रतिलिपि क्या है?"

एक आदिम प्रकार के बारे में सोचें u8: एक बाइट कॉपी सरल है, बस एक बाइट कॉपी करें, और एक सिमेंटिक कॉपी सरल है, सिंगल बाइट कॉपी करें। विशेष रूप से, एक बाइट प्रति है एक अर्थ प्रतिलिपि ... जंग भी है एक निर्मित विशेषताCopy है कि कैप्चर जो प्रकार समान अर्थ और बाइट प्रतियां।

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

let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine

पुराना : NoCopyमार्कर कंपाइलर के स्वचालित व्यवहार को उस प्रकार के मानने से रोकता है जो हो सकता है Copy(यानी केवल आदिम के समुच्चय वाले &) Copy। हालाँकि, यह तब बदल जाएगा जब ऑप्ट-इन अंतर्निहित लक्षण लागू होते हैं।

जैसा कि ऊपर उल्लेख किया गया है, ऑप्ट-इन अंतर्निहित लक्षण लागू होते हैं, इसलिए संकलक के पास अब स्वचालित व्यवहार नहीं है। हालांकि, अतीत में स्वत: व्यवहार के लिए इस्तेमाल किया जाने वाला नियम यह जांचने के लिए समान नियम हैं कि क्या इसे लागू करना कानूनी है Copy


@dbaupp: क्या आपको पता होगा कि जंग के किस संस्करण में ऑप्ट-इन अंतर्निहित लक्षण दिखाई दिए थे? मुझे लगता है कि 0.10 होगा।
Matthieu M.

@MatthieuM। इसे अभी तक लागू नहीं किया गया है, और वास्तव में ऑप्ट-इन बिल्ट-इन के डिजाइन के लिए हाल ही में कुछ प्रस्तावित संशोधन हुए हैं
हून

मुझे लगता है कि पुरानी बोली को मिटा देना चाहिए।
स्टारगेटर

1
# # व्युत्पन्न (कॉपी, क्लोन)] का प्रयोग
ट्रिपलटी

6

सबसे आसान तरीका है कि अपने प्रकार में कुछ एम्बेड करें जो कि प्रतिलिपि बनाने योग्य नहीं है।

मानक पुस्तकालय वास्तव में इस उपयोग के मामले के लिए "मार्कर प्रकार" प्रदान करता है: NoCopy । उदाहरण के लिए:

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
    nocopy: NoCopy,
}

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