कुछ दिनों पहले, 0xd34df00d ने यहां एक लेख का अनुवाद प्रकाशित किया, जिसमें बताया गया है कि आप विभिन्न भाषाओं में किसी फ़ंक्शन के बारे में क्या सीख सकते हैं, यदि आप इसे "ब्लैक बॉक्स" मानते हैं, तो इसके कार्यान्वयन के बारे में जानकारी का उपयोग किए बिना (लेकिन, निश्चित रूप से, संकलक का उपयोग किए बिना)। बेशक, प्राप्त जानकारी भाषा पर बहुत निर्भर है - मूल लेख में चार उदाहरणों पर विचार किया गया था:
- पायथन - गतिशील रूप से टाइप की गई, न्यूनतम जानकारी, केवल परीक्षण कुछ संकेत देते हैं;
- सी - कमजोर सांख्यिकीय रूप से टाइप किया गया, बहुत अधिक जानकारी नहीं;
- हास्केल - शुद्ध कार्यों के साथ दृढ़ता से टाइप किया गया, बहुत अधिक जानकारी;
- इदरीस आश्रित प्रकारों वाली भाषा है, संकलन के दौरान फ़ंक्शन की शुद्धता साबित करने के लिए पर्याप्त जानकारी है।
"सी है, हस्केल है, लेकिन रस्ट कहाँ है!" - तुरंत सवाल उठाया गया। जवाब कट के नीचे है।
समस्या की स्थिति को याद करें:
एक सूची और कुछ अर्थ दिया जाए। सूची में इस मूल्य के सूचकांक को वापस करना आवश्यक है या संकेत मिलता है कि यह मूल्य सूची में नहीं है।
अधीर के लिए - नीचे चर्चा किए गए सभी विकल्प रस्ट खेल के मैदान में देखे जा सकते हैं।
चलो चलते हैं!
सरल खोज
हम लगभग भोले हस्ताक्षर के साथ शुरू करेंगे, जो वास्तव में, केवल कुछ मुहावरेदार तत्वों में सी कोड से अलग है:
fn foo(x: &[i32], y: i32) -> Option<usize> {
हम इस सुविधा के बारे में क्या जानते हैं? खैर ... बहुत नहीं, वास्तव में। बेशक, रिटर्न मानों में Option<usize>
होने से बेहतर है कि C ने हमें क्या प्रदान किया, लेकिन हमें अभी भी फ़ंक्शन के शब्दार्थ के बारे में कोई जानकारी नहीं है। विशेष रूप से, इस बात की कोई गारंटी नहीं है कि कोई दुष्प्रभाव नहीं होगा, और न ही अपेक्षित व्यवहार को सत्यापित करने का कोई तरीका हो सकता है।
क्या एक सही तरीके से लिखित परीक्षा स्थिति को ठीक कर सकती है? हम देखते हैं:
#[test] fn test() { assert_eq!(foo(&[1, 2, 3], 2), Some(1)); assert_eq!(foo(&[1, 2, 3], 4), None); }
सामान्य तौर पर, हमें कुछ भी नया नहीं मिला - सभी समान जाँचें जिन्हें हम आसानी से पाइथन के साथ कर सकते हैं (और, आगे देखते हुए, परीक्षण भविष्य में व्यावहारिक रूप से कुछ भी नहीं देंगे)।
जेनरिक का प्रयोग करें, ल्यूक!
लेकिन क्या यह वास्तव में अच्छा है कि हम केवल हस्ताक्षरित 32-बिट संख्या का उपयोग करने के लिए मजबूर हैं? गड़बड़। हम ठीक करते हैं:
fn foo<El>(x: &[El], y: El) -> Option<usize> where El: PartialEq, {
अहा! यह पहले से ही कुछ है। अब हम स्लाइस ले सकते हैं, जिसमें ऐसे तत्व शामिल हैं जिनकी हम समानता के लिए तुलना कर सकते हैं। स्पष्ट बहुरूपता लगभग हमेशा निहित से बेहतर होता है और लगभग हमेशा किसी से बेहतर नहीं होता है, क्या यह है?
हालांकि, इस तरह के एक समारोह अप्रत्याशित रूप से हमारे लिए निम्नलिखित परीक्षा पास कर सकता है:
fn refl<El: PartialEq + Copy>(el: El) -> Option<usize> { foo(&[el], el)
यह तुरंत हमारे हिस्से में कुछ दोष को इंगित करता है, क्योंकि मूल विनिर्देश के अनुसार, इस तरह के कॉल को Some(0)
वापस करना होगा। बेशक, यहां समस्या सामान्य रूप से सामान्य रूप से परिभाषित तुलना और विशेष रूप से तैरने वाले प्रकारों की विशिष्टता के कारण है।
मान लीजिए कि अब हम इस तरह की समस्या से निजात पाना चाहते हैं - इसके लिए हम सिर्फ एल टाइप की जरूरतों को पूरा करते हैं:
fn foo<El>(x: &[El], y: El) -> Option<usize> where El: Eq, {
अब हम सिर्फ समानता के लिए तुलना की संभावना की मांग नहीं करते हैं - हमें आवश्यकता है कि यह तुलना समतुल्य संबंध हो । यह कुछ हद तक संभव मापदंडों की सीमा को बताता है, लेकिन अब दोनों प्रकार और परीक्षण सुझाव देते हैं (यद्यपि स्पष्ट रूप से संकेत नहीं करते हैं) कि अपेक्षित व्यवहार वास्तव में विनिर्देश में गिरना चाहिए।
पाचन: हम अधिक सामान्य जाना चाहते हैं!यह विकल्प मूल कार्य से संबंधित नहीं है, लेकिन, मेरी राय में, सिद्धांत का एक अच्छा चित्रण है: "जो आप स्वीकार करते हैं उसमें उदार हो, जो आप करते हैं उसमें रूढ़िवादी हो"। दूसरे शब्दों में: यदि कोई अवसर है, तो एर्गोनॉमिक्स और प्रदर्शन के पक्षपात के बिना, स्वीकृत मूल्यों के प्रकार को और अधिक सामान्य बनाने के लिए - यह सिर्फ ऐसा करने के लिए समझ में आता है।
इस विकल्प पर विचार करें:
fn foo<'a, El: 'a>(x: impl IntoIterator<Item = &'a El>, y: El) -> Option<usize> where El: Eq, {
अब हम इस फ़ंक्शन के बारे में क्या जानते हैं? सब कुछ समान है, केवल अब यह एक सूची या इनपुट के रूप में एक स्लाइस को स्वीकार नहीं करता है, लेकिन कुछ मनमानी वस्तु, जो टाइप एल की वस्तुओं के लिए वैकल्पिक रूप से लिंक देने और उन्हें मांगी गई एक के साथ तुलना करने के लिए बनाया जा सकता है: जावा में एनालॉग, अगर मुझे सही याद है, तो एक ऐसा कार्य होगा जो एक Iterable<Comparable>
लेता है।
पहले की तरह, केवल कठोर
हालांकि, उदाहरण के लिए, पहले से ही ज्ञात चरणों में कंपाइलर द्वारा दी गई गारंटी हमारे लिए पर्याप्त नहीं है। या, कहने दें, हम ढेर में जाने के लिए (एक कारण या किसी अन्य के लिए) नहीं चाहते हैं, लेकिन ढेर पर काम करना चाहते हैं - जिसका अर्थ है कि हमें एक वेक्टर के बजाय एक सरणी की आवश्यकता है - लेकिन साथ ही हम चाहते हैं कि हमारा कोड सरणी के विभिन्न आकारों में सामान्यीकृत हो। । या हम चाहते हैं कि इनपुट सूची के प्रत्येक विशिष्ट आकार के लिए फ़ंक्शन को जितना संभव हो उतना अनुकूलित किया जाए।
संक्षेप में, हमें एक सामान्य सरणी की आवश्यकता है - और रस्ट में पहले से ही एक पैकेज है जो इसे शब्दशः प्रदान करता है।
अब हमारे पास हमारे कोड में निम्नलिखित कोड हैं:
use generic_array::{GenericArray, ArrayLength}; fn foo<El, Size>(x: GenericArray<El, Size>, y: El) -> Option<usize> where El: Eq, Size: ArrayLength<El>, {
हम इस कोड से क्या जानते हैं? यह फ़ंक्शन अपने आकार में परिलक्षित (और प्रत्येक ऐसे आकार के लिए स्वतंत्र रूप से संकलित) कुछ निश्चित आकार की एक सरणी लेता है। अब तक, यह बहुत कुछ नहीं बदलता है - अंत में, एक ही गारंटी देता है, न केवल मोनोमोर्फिफ़ेशन चरण में, बल्कि रनटाइम में, पिछले संस्करण को एक कट के साथ प्रदान किया।
लेकिन हम आगे भी जा सकते हैं।
प्रकार स्तर अंकगणित
मूल लेख में कई गारंटी का जिक्र था जो हमें इदरीस से मिली थी और किसी और से नहीं मिल सकती थी। उनमें से एक - और शायद सबसे सरल एक, क्योंकि इसके लिए आपको पूर्ण-लिखित प्रमाण या पूर्ण-लिखित परीक्षा लिखने की भी आवश्यकता नहीं है, लेकिन केवल प्रकार को थोड़ा निर्दिष्ट करने के लिए - यह कहता है कि रिटर्न मान, यदि मौजूद है (अर्थात यदि यह Nothing
), इनपुट सूची की लंबाई से अधिक नहीं होने की गारंटी है।
ऐसा लगता है कि इस तरह की गारंटी के लिए आवश्यक शर्त निर्भर प्रकारों की उपस्थिति है, अच्छी तरह से, या कम से कम किसी प्रकार की समानता है, और यह सही है कि रुस्ट से कुछ इस तरह की उम्मीद करना अजीब होगा?
मिलना - टाइपेनम । इसके साथ, हमारे कार्य को इस तरह दर्शाया जा सकता है:
use generic_array::{ArrayLength, GenericArray}; use typenum::{IsLess, Unsigned, B1}; trait UnsignedLessThan<T> { fn as_usize(&self) -> usize; } impl<Less, More> UnsignedLessThan<More> for Less where Less: IsLess<More, Output = B1>, Less: Unsigned, { fn as_usize(&self) -> usize { <Self as Unsigned>::USIZE } } fn foo<El, Size>(x: GenericArray<El, Size>, y: El) -> Option<Box<dyn UnsignedLessThan<Size>>> where El: Eq, Size: ArrayLength<El>, {
"यह काला जादू क्या है?" - आप पूछें। और आप निश्चित रूप से सही होंगे: टाइपेनम वह काला जादू है, और कम से कम किसी तरह से इसका उपयोग करने का प्रयास दोगुना है।
फिर भी, इस फ़ंक्शन के हस्ताक्षर काफी अस्पष्ट हैं।
- फ़ंक्शन लंबाई के एल तत्वों के एक सरणी और प्रकार एल के एक तत्व को स्वीकार करता है।
- फ़ंक्शन एक विकल्प मान लौटाता है, जो, यदि यह कुछ है,
- यह
UnsignedLessThan<T>
पर आधारित एक विशेषता वस्तु है , जो एक पैरामीटर के रूप में आकार प्रकार को स्वीकार करता है; - बदले में,
UnsignedLessThan<T>
IsLess<T>
सभी प्रकारों के लिए लागू की जाती है जो Unsigned
और IsLess<T>
को लागू करते हैं, जिसके लिए IsLess<T>
B1 देता है, अर्थात सच।
दूसरे शब्दों में, इस तरह से हमने एक फ़ंक्शन लिखा है जो सरणी के मूल आकार की तुलना में एक गैर-नकारात्मक (अहस्ताक्षरित) संख्या को वापस करने की गारंटी है (या यक़ीनन, यह उसी विशेषता ऑब्जेक्ट को लौटाता है, जहाँ से हमें बाद में as_usize
विधि को कॉल करना चाहिए, ऐसी संख्या वापस करने की गारंटी है) ।
इस दृष्टिकोण में दो चालें हैं:
- हम प्रदर्शन में उल्लेखनीय रूप से हार सकते हैं। यदि अचानक, किसी कारण से, इस तरह का हमारा कार्य कार्यक्रम के "गर्म" हिस्से में है, तो गतिशील कॉल की निरंतर आवश्यकता सबसे धीमी संचालन में से एक हो सकती है। हालाँकि, यह कमी उतनी महत्वपूर्ण नहीं हो सकती जितनी कि लगती है, लेकिन एक दूसरा भी है:
- इस फ़ंक्शन को सही तरीके से संकलित करने के लिए, हमें वास्तव में इसके अंदर अपने काम की शुद्धता का प्रमाण लिखना होगा, या
unsafe
माध्यम से टाइप सिस्टम को "ट्रिक" करना होगा। पहला शुक्रवार लेख के लिए बहुत जटिल है, लेकिन दूसरा केवल एक घोटाला है।
निष्कर्ष
बेशक, व्यवहार में, ऐसे मामलों में, या तो दूसरा कार्यान्वयन (एक मनमाना प्रकार का एक टुकड़ा प्राप्त करना) या एक स्पॉइलर के तहत कार्यान्वयन (प्राप्त करने योग्य वस्तु प्राप्त करना) का उपयोग किया जाएगा। बाद के सभी तर्क लगभग निश्चित रूप से व्यावहारिक हित के नहीं हैं और केवल एक प्रकार की प्रणाली के साथ काम करने में एक अभ्यास के रूप में काम करते हैं।
फिर भी, तथ्य यह है कि जंग प्रकार प्रणाली पर स्पष्ट रूप से मजबूत इदरिस प्रकार प्रणाली की विशेषताओं में से एक का अनुकरण करने में सक्षम हो सकता है, मेरी राय में, काफी सांकेतिक।