Rust में &String, &Vec और &Box का उपयोग क्यों गलत है?

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

परिचय: Rust सीखने वालों की आम गलती

\n\n

आप Rust सीख रहे हैं। आपने ownership और borrowing के कॉन्सेप्ट को समझ लिया है। अब आप अपना पहला गंभीर फंक्शन लिख रहे हैं और सोचते हैं: "अच्छा, स्ट्रिंग को मूव करने से बचने के लिए, मैं एक रेफरेंस पास करूंगा!" और आप लिखते हैं:

\n\n
fn print_string(s: &String) {\n    println!("{}", s);\n}
\n\n

कंपाइलर खुश है। कोड काम करता है। आप खुश हैं। लेकिन समस्या यह है: कोई अनुभवी Rust डेवलपर आपका कोड देखकर नाक-भौं सिकोड़ेगा। क्यों? आखिर सब कुछ तो काम कर रहा है!

\n\n

यह सवाल StackOverflow पर सबसे लोकप्रिय सवालों में से एक है। हज़ारों डेवलपर इस छोटी सी बात पर अटक जाते हैं। आज हम समझेंगे कि फंक्शन सिग्नेचर में &String, &Vec और &Box का उपयोग क्यों एक रेड फ्लैग है, और सही तरीका क्या है।

\n\n

समस्या #1: टाइप्स की अनावश्यक सीमा (Type Coercion)

\n\n

Rust में एक शक्तिशाली ऑटोमैटिक टाइप कन्वर्जन सिस्टम है, लेकिन यह केवल एक दिशा में काम करता है। इसे Deref Coercion (डीरेफरेंस के ज़रिए कन्वर्जन) कहते हैं।

\n\n

बात सीधी है: अगर आपके पास String का रेफरेंस है, तो Rust ऑटोमैटिकली इसे &str के रेफरेंस में बदल सकता है। यही बात &Vec<T> → &[T] और &Box<T> → &T पर भी लागू होती है।

\n\n

फर्क देखें:

\n\n
// गलत: केवल &String स्वीकार करता है\nfn bad(s: &String) {\n    println!("{}", s);\n}\n\n// सही: &str स्वीकार करता है\nfn good(s: &str) {\n    println!("{}", s);\n}\n\nfn main() {\n    let owned = String::from("नमस्ते, दुनिया!");\n    let slice = "नमस्ते, स्लाइस!";\n    \n    bad(&owned); // काम करता है\n    // bad(slice); // त्रुटि! slice एक &str है, &String नहीं\n    \n    good(&owned); // काम करता है (ऑटोमैटिक कन्वर्जन)\n    good(slice);   // काम करता है (यह पहले से &str है)\n}
\n\n

देखा? फंक्शन good किसी भी स्ट्रिंग को स्वीकार करता है: String के मालिक को, स्ट्रिंग लिटरल &str को, और स्ट्रिंग के एक हिस्से को भी। फंक्शन bad केवल String को स्वीकार करता है। यह एक अनावश्यक सीमा है।

\n\n

व्यावहारिक उदाहरण: सबस्ट्रिंग खोजना

\n\n
// सीमित खोज\nfn find_in_string(haystack: &String, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\n// सार्वभौमिक खोज\nfn find_in_str(haystack: &str, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\nfn main() {\n    let my_string = String::from("Rust is awesome");\n    let my_str = "Rust is awesome";\n    \n    // केवल String के साथ काम करता है\n    find_in_string(&my_string, "Rust");\n    // find_in_str(my_str, "Rust"); // कंपाइलेशन एरर!\n    \n    // दोनों के साथ काम करता है\n    find_in_str(&my_string, "Rust");\n    find_in_str(my_str, "Rust");\n}
\n\n

समस्या #2: परफॉरमेंस और अनावश्यक इनडायरेक्शन

\n\n

चलिए "हुड के नीचे" देखते हैं। मेमोरी में String क्या है? यह तीन फील्ड्स वाली एक स्ट्रक्चर है:

\n
    \n
  • हीप में डेटा का पॉइंटर
  • \n
  • स्ट्रिंग की लंबाई
  • \n
  • क्षमता (capacity)
  • \n
\n\n

जब आप &String पास करते हैं, तो आप इस स्ट्रक्चर का रेफरेंस पास कर रहे हैं। डेटा तक पहुंचने के लिए कंपाइलर को:

\n
    \n
  1. &String को डीरेफरेंस करना → String स्ट्रक्चर प्राप्त करना
  2. \n
  3. स्ट्रक्चर से हीप में डेटा का पॉइंटर लेना
  4. \n
  5. उस पॉइंटर को डीरेफरेंस करना → स्ट्रिंग के बाइट्स प्राप्त करना
  6. \n
\n\n

जब आप &str पास करते हैं, तो आप एक fat pointer (मोटा पॉइंटर) पास कर रहे हैं, जिसमें केवल दो फील्ड्स होते हैं:

\n
    \n
  • डेटा का पॉइंटर
  • \n
  • लंबाई
  • \n
\n\n

कंपाइलर को केवल एक कदम उठाना होता है — पॉइंटर को डीरेफरेंस करना। कम इनडायरेक्शन का मतलब है तेज़ कोड। यह फर्क बहुत छोटा है, लेकिन हॉट लूप्स या बार-बार फंक्शन कॉल में यह मायने रखता है।

ब्लॉग

पुस्तक अनुशंसाएँ