Rust में Generics: क्यों ज़रूरी हैं और व्यवहार में कैसे उपयोग करें
\n\nसामान्यीकृत प्रोग्रामिंग (Generics) Rust की एक प्रमुख क्षमता है, जो लचीला, पुन: प्रयोज्य और सुरक्षित कोड लिखने की अनुमति देती है। इस लेख में, हम समझेंगे कि Generics क्यों ज़रूरी हैं, वे अंदरूनी रूप से कैसे काम करते हैं, और वास्तविक प्रोजेक्ट्स में उनका उपयोग कैसे करें। यह सामग्री Rust सीखने वाले शुरुआती और अपने ज्ञान को व्यवस्थित करने वाले अनुभवी डेवलपर्स दोनों के लिए उपयोगी होगी।
\n\nRust में Generics क्यों ज़रूरी हैं?
\n\nकल्पना करें कि आपको एक फ़ंक्शन लिखना है जो किसी सरणी में सबसे बड़ा तत्व ढूंढता है। Generics के बिना, आपको प्रत्येक डेटा प्रकार के लिए अलग-अलग संस्करण बनाने होंगे: i32, f64, String इत्यादि। इससे कोड की पुनरावृत्ति होती है और रखरखाव जटिल हो जाता है।
Generics प्रकारों को पैरामीटराइज़ करने की अनुमति देकर इस समस्या का समाधान करते हैं। यह लिखने के बजाय:
\n\nfn find_max_i32(list: &[i32]) -> i32 { ... }\nfn find_max_f64(list: &[f64]) -> f64 { ... }\n\nआप एक सार्वभौमिक फ़ंक्शन लिखते हैं:
\n\nfn find_max<T: PartialOrd>(list: &[T]) -> &T { ... }\n\nGenerics के मुख्य लाभ:
\n\n- \n
- कोड का पुन: उपयोग — एक फ़ंक्शन या संरचना विभिन्न प्रकारों के साथ काम करती है \n
- संकलन चरण पर प्रकार सुरक्षा — सभी प्रकार की त्रुटियाँ प्रोग्राम चलने से पहले पता चल जाती हैं \n
- प्रदर्शन — Rust मोनोमॉर्फ़िज़ेशन का उपयोग करता है, प्रत्येक विशिष्ट प्रकार के लिए अनुकूलित कोड बनाता है \n
- पठनीयता — कम पुनरावृत्ति, कोड अधिक संक्षिप्त हो जाता है \n
Generics कैसे काम करते हैं: मोनोमॉर्फ़िज़ेशन
\n\nRust सामान्यीकृत कोड को एक विशेष तरीके से संकलित करता है — मोनोमॉर्फ़िज़ेशन के माध्यम से। इसका मतलब है कि कंपाइलर प्रोग्राम में उपयोग किए जाने वाले प्रत्येक विशिष्ट प्रकार के लिए फ़ंक्शन या संरचना की एक अलग प्रतिलिपि बनाता है।
\n\nउदाहरण:
\n\nfn print_type<T>(val: T) {\n println!("मान: {}", val);\n}\n\nfn main() {\n print_type(42); // T = i32\n print_type(3.14); // T = f64\n print_type("नमस्ते"); // T = &str\n}\n\nसंकलन के बाद, Rust print_type के तीन संस्करण बनाएगा:
- \n
print_type_i32\n print_type_f64\n print_type_str\n
यह गारंटी देता है कि सामान्यीकृत कोड प्रदर्शन में विशिष्ट कोड से कमतर नहीं है। एकमात्र "कमी" बाइनरी फ़ाइल के आकार में वृद्धि है, लेकिन व्यवहार में यह शायद ही कभी कोई समस्या बनती है।
\n\nफ़ंक्शन और संरचनाओं के साथ Generics का उपयोग
\n\nफ़ंक्शन में Generics
\n\nGenerics का उपयोग करने का सबसे सरल तरीका फ़ंक्शन को पैरामीटराइज़ करना है:
\n\nfn swap<T>(a: &mut T, b: &mut T) {\n let temp = std::mem::replace(a, unsafe { std::mem::zeroed() });\n *a = std::mem::replace(b, temp);\n *b = temp;\n}\n\nfn main() {\n let mut x = 10;\n let mut y = 20;\n swap(&mut x, &mut y);\n println!("x = {}, y = {}", x, y); // x = 20, y = 10\n}\n\nध्यान दें: हम प्रकार निर्दिष्ट नहीं करते — कंपाइलर तर्कों के आधार पर स्वचालित रूप से इसका अनुमान लगाता है।
\n\nसंरचनाओं में Generics
\n\nसंरचनाएँ भी सामान्यीकृत हो सकती हैं। यह डेटा कंटेनरों के लिए विशेष रूप से उपयोगी है:
\n\nstruct Point<T> {\n x: T,\n y: T,\n}\n\nimpl<T> Point<T> {\n fn new(x: T, y: T) -> Self {\n Point { x, y }\n }\n\n fn distance_from_origin(&self) -> f64\n where\n T: Into<f64> + Copy,\n {\n let x: f64 = self.x.into();\n let y: f64 = self.y.into();\n (x * x + y * y).sqrt()\n }\n}\n\nfn main() {\n let int_point = Point::new(3, 4);\n let float_point = Point::new(3.0, 4.0);\n \n // distance_from_origin केवल उन प्रकारों के लिए काम करता है जो Into<f6