Rust में "Use of moved value" एरर: क्यों होती है और कैसे ठीक करें?
\n\nअगर आपने Rust सीखना शुरू किया है, तो शायद आपको यह रहस्यमयी मैसेज मिला होगा: error[E0382]: use of moved value। कंपाइलर गुस्सा होता है, आप सिर खुजाते हैं, और जो कोड लॉजिकली बिल्कुल सही है, वह कंपाइल होने से इनकार कर देता है। परिचित लग रहा है? Rust की ownership और borrowing की दुनिया में आपका स्वागत है — ये मुख्य अवधारणाएँ हैं जो Rust को सुरक्षित बनाती हैं, लेकिन कभी-कभी अनुभवी डेवलपर्स को भी हैरान कर देती हैं।
यह लेख कोई सूखा डॉक्यूमेंटेशन नहीं है, बल्कि समस्या का एक व्यावहारिक विश्लेषण है। हम देखेंगे कि use of moved value एरर क्यों होती है, असली कोड उदाहरणों के साथ, और सबसे महत्वपूर्ण बात — इसका समाधान क्या है। आप सीखेंगे कि borrow checker की तरह कैसे सोचें और उससे लड़ना कैसे बंद करें। चलिए शुरू करते हैं!
\n\nRust में "move" क्या है और कंपाइलर इतना सख्त क्यों है?
\n\nRust में हर वैल्यू का एक मालिक (owner) होता है। जब आप किसी वैल्यू को दूसरे वेरिएबल को असाइन करते हैं या फंक्शन में भेजते हैं, तो ownership स्थानांतरित (move) हो जाती है। स्थानांतरण के बाद, मूल वेरिएबल अमान्य हो जाता है — कंपाइलर उसे एक्सेस करने से रोकता है। यही use of moved value एरर की जड़ है।
\n\nएक सरल उदाहरण देखें:
\n\nlet s1 = String::from("नमस्ते");\nlet s2 = s1; // Ownership s1 से s2 में स्थानांतरित हो गई\nprintln!("{}", s1); // एरर: use of moved value: `s1`\n\nऐसा क्यों? String स्टैक पर एक साधारण कॉपी नहीं है। यह हीप (heap) पर डेटा स्टोर करता है। अगर Rust सिर्फ पॉइंटर कॉपी करता, तो मेमोरी फ्री करते समय दोनों वेरिएबल उसे डिलीट करने की कोशिश करते — double free। इसके बजाय, Rust ownership को "स्थानांतरित" करता है: अब s2 मेमोरी के लिए जिम्मेदार है, और s1 कंपाइलर की नज़र में मौजूद नहीं है। यह Rust में move semantics की मूल अवधारणा है।
एरर की ओर ले जाने वाले सामान्य परिदृश्य
\n\nuse of moved value एरर अक्सर तीन स्थितियों में होती है:
- \n
- फंक्शन में वैल्यू भेजना \n
- दूसरे वेरिएबल को वैल्यू असाइन करना \n
- लूप या इटरेटर में वैल्यू का उपयोग करना \n
प्रत्येक मामले को विस्तार से देखते हैं।
\n\n1. बिना रिटर्न के फंक्शन में भेजना
\n\nfn print_string(s: String) {\n println!("{}", s);\n}\n\nfn main() {\n let my_string = String::from("Rust");\n print_string(my_string); // ownership स्थानांतरित हो गई\n println!("{}", my_string); // एरर! use of moved value\n}\n\nprint_string को कॉल करने के बाद, my_string की ownership फंक्शन में चली गई। जब फंक्शन खत्म होता है, s डिलीट हो जाता है, और my_string अमान्य हो जाता है। कंपाइलर आपको मृत ऑब्जेक्ट का उपयोग करने से बचाता है।
2. बिना कॉपी किए असाइन करना
\n\nहमने String के साथ यह उदाहरण देखा। लेकिन ध्यान दें: सभी टाइप ऐसा व्यवहार नहीं करते। i32, bool, f64 जैसे सरल टाइप Copy ट्रेट को इम्प्लीमेंट करते हैं। वे स्थानांतरित नहीं होते, बल्कि ऑटोमैटिकली कॉपी हो जाते हैं।
let x = 5;\nlet y = x; // कॉपी, स्थानांतरण नहीं\nprintln!("{}", x); // काम करता है!\n\nजो टाइप हीप पर डेटा स्टोर करते हैं (String, Vec, Box), वे Copy को इम्प्लीमेंट नहीं करते — केवल Clone करते हैं। इसलिए वे स्थानांतरित होते हैं।
3. कलेक्शन पर इटरेट करना
\n\nएक और आम एरर स्रोत for लूप है:
let v = vec![1, 2, 3];\nfor x in v { // v इटरेटर में स्थानांतरित हो गया\n println!("{}", x);\n}\nprintln!("{:?}", v); // एरर! use of moved value\n\nजब आप for x in v लिखते हैं, तो आप वास्तव में into_iter() कॉल कर रहे होते हैं, जो वेक्टर को खपत (consume) करता है। लूप के बाद v मौजूद नहीं रहता।
समाधान: 4 सिद्ध तरीके
\n\nअब हम