Rust में शून्य का वेक्टर बनाना: हुड के नीचे का जादू

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

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

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

Rust में शून्य का वेक्टर: जितना लगता है उतना आसान नहीं

\n\n

ऐसा लगता है, शून्य से भरा वेक्टर बनाने से आसान क्या हो सकता है? Python में यह एक पंक्ति में किया जाता है: [0] * n। JavaScript में — Array(n).fill(0)। लेकिन जब आप Rust पर आते हैं, तो ऐसा सामान्य कार्य भी सिस्टम प्रोग्रामिंग, मेमोरी प्रबंधन और अमूर्तता पर शून्य लागत की एक पूरी दुनिया खोल देता है।

\n\n

Stack Overflow पर प्रश्न "Creating a vector of zeros for a specific size" ने हजारों बार देखा गया है। और यह आकस्मिक नहीं है: एक सरल प्रश्न के पीछे Rust के मेमोरी, आरंभीकरण और सुरक्षा के साथ काम करने की गहरी समझ छिपी है। आज हम Rust के हुड के नीचे झांकेंगे और शून्य का वेक्टर बनाने के सभी तरीकों को समझेंगे, और सबसे महत्वपूर्ण बात, हम समझेंगे कि वास्तव में RAM में क्या होता है।

\n\n

यह Python से अधिक जटिल क्यों है?

\n\n

Python या JavaScript में, आप बस एक ऐरे बनाते हैं, और इंटरप्रेटर आपके लिए सारा गंदा काम करता है। Rust एक सिस्टम-स्तरीय भाषा है। यह मेमोरी को अप्रारंभीकृत (uninitialized) छोड़ने की अनुमति नहीं देता (जब तक कि आप unsafe का उपयोग नहीं करते)। प्रत्येक बाइट या तो स्पष्ट रूप से भरा जाना चाहिए, या "अपरिभाषित व्यवहार" के रूप में चिह्नित किया जाना चाहिए।

\n\n

कल्पना करें: आप 100 खाली बक्से ऑर्डर करते हैं। Python में, आपको बस 100 खाली बक्से मिलेंगे। Rust में, कूरियर पूछेगा: "प्रत्येक के अंदर क्या रखूं?" — और यदि आप नहीं बताते, तो वह बस नहीं आएगा। यह गारंटी देता है कि आप कभी भी गलती से मेमोरी से "कचरा" नहीं पढ़ेंगे जो पिछले प्रोग्राम से बचा हुआ है।

\n\n

विधि #1: क्लासिक vec![0; size]

\n\n

सबसे स्पष्ट और अनुशंसित तरीका vec! मैक्रो का उपयोग करना है:

\n\n
let zeros: Vec<i32> = vec![0; 10];\nprintln!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
\n\n

संक्षिप्त, सुरुचिपूर्ण, स्पष्ट। लेकिन हुड के नीचे क्या होता है? आइए चरण दर चरण समझते हैं।

\n\n

vec![0; 10] के हुड के नीचे

\n\n

जब आप vec![0; 10] लिखते हैं, Rust निम्नलिखित करता है:

\n\n
    \n
  1. मेमोरी आवंटित करता है हीप (heap) पर 10 i32 प्रकार के तत्वों के लिए। प्रत्येक i32 4 बाइट लेता है, कुल 40 बाइट।
  2. \n
  3. जाँच करता है कि प्रकार Clone ट्रेट को लागू करता है। संख्याएँ i32 इसे लागू करती हैं, इसलिए कंपाइलर प्रत्येक तत्व के लिए मान 0 की प्रतिलिपि बना सकता है।
  4. \n
  5. प्रतिलिपि लूप शुरू करता है alloc::vec::from_elem के अंदर। कंपाइलर इस लूप को memset में अनुकूलित करता है (यदि प्रकार एक सरल अदिश है)।
  6. \n
  7. Vec लौटाता है जिसकी लंबाई 10 और क्षमता 10 है।
  8. \n
\n\n

मुख्य बिंदु: मेमोरी अप्रारंभीकृत नहीं रहती। Rust गारंटी देता है कि वेक्टर तक पहुँचने से पहले प्रत्येक बाइट शून्य से भरा होगा।

\n\n

विधि #2: vec![Default::default(); size]

\n\n

यदि आपका प्रकार कोई संख्या नहीं है, लेकिन Default ट्रेट को लागू करता है, तो आप सामान्यीकृत दृष्टिकोण का उपयोग कर सकते हैं:

\n\n
#[derive(Default)]\nstruct Point {\n    x: f64,\n    y: f64,\n}\n\nlet points: Vec<Point> = vec![Default::default(); 5];\nprintln!("{:?}", points[0]); // Point { x: 0.0, y: 0.0 }
\n\n

आदिम प्रकारों के लिए Default::default() 0, 0.0, false या '\\0' देता है। उपयोगकर्ता-परिभाषित संरचनाओं के लिए — वह जो आपने #[derive(Default)] में परिभाषित किया है।

\n\n

प्रदर्शन के बारे में क्या?

\n\n

आइए एक सरल बेंचमार्क लिखें (वास्तविक कोड में criterion का उपयोग करें):

\n\n
use std::time::Instant;\n\nfn main() {\n    let n = 1_000_000;\n\n    let start = Instant::now();\n    let v1 = vec![0u8; n];\n    let dur1 = start.elapsed();\n\n    let start = Instant::now();\n    let v2 = vec![0u64; n];\n    let dur2 = start.elapsed();\n\n    println!("u8:  {:?}", dur1);\n    println!("u64: {:?}", dur2);\n}
\n\n

में

ब्लॉग

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