प्रस्तावना : यह उत्तर ऑप्ट-इन अंतर्निहित लक्षणों से पहले लिखा गया था - विशेष रूप से 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
।