जंग की मुख्य विशेषताएं

रस्ट मोज़िला कॉर्पोरेशन द्वारा विकसित एक नई प्रोग्रामिंग भाषा है। डेवलपर्स का मुख्य लक्ष्य समानांतर कंप्यूटिंग के लिए एक सुरक्षित और व्यावहारिक भाषा बनाना है। भाषा का पहला संस्करण 2006 में ग्रिडोन चोइर द्वारा लिखा गया था, और 2009 में मोज़िला विकास में शामिल हो गया। तब से, कंपाइलर ही, मूल रूप से OCaml में लिखा गया था, इसमें भी बदलाव आए हैं: इसे Rust में LLVM के बैक-एंड के रूप में सफलतापूर्वक पुनः लिखा गया है।

रस्ट में विकसित किया जा रहा मुख्य उत्पाद नया सर्वो वेब इंजन है, जिसे मोज़िला द्वारा भी विकसित किया जा रहा है। 2013 में, सैमसंग इलेक्ट्रॉनिक्स कॉर्पोरेशन रस्ट एंड सर्वो के विकास में शामिल हो गया, जिसकी सक्रिय भागीदारी के साथ सर्वो इंजन कोड को एआरएम आर्किटेक्चर में पोर्ट किया गया। आईटी उद्योग में ऐसे गंभीर खिलाड़ियों द्वारा भाषा का समर्थन आनन्दित नहीं कर सकता है, और इसके आगे सक्रिय विकास और सुधार के लिए आशा देता है।

रूस्ट भाषा केवल सिस्टम और नेटवर्क डेवलपर्स द्वारा पसंद नहीं की जा सकती है, जिन्हें काम पर बहुत सारे कोड लिखने होते हैं, जिनका प्रदर्शन C और C ++ में महत्वपूर्ण है, क्योंकि:
  1. रस्ट सुरक्षित अनुप्रयोगों के विकास पर केंद्रित है। इसमें मेमोरी के साथ सुरक्षित कार्य शामिल हैं: अशक्त बिंदुओं की अनुपस्थिति, गैर-आरंभिक और डी-इनिशियलाइज्ड चर के उपयोग पर नियंत्रण; कई कार्यों के साथ साझा किए गए राज्यों को साझा करने की असंभवता; सूचक जीवनकाल का स्थैतिक विश्लेषण।
  2. रस्ट समानांतर अनुप्रयोगों के विकास पर केंद्रित है। यह प्रकाश (हरा) प्रवाह, अतुल्यकालिक संदेश भेजने के लिए समर्थन को लागू करता है बिना भेजे गए डेटा को कॉपी करता है, स्टैक पर वस्तुओं के स्थान को चुनने की क्षमता, स्थानीय कार्य ढेर में, या कार्यों के बीच साझा किए गए ढेर में।
  3. जंग गति और स्मृति कुशल अनुप्रयोगों के विकास पर केंद्रित है। LLVM का उपयोग बैक-एंड के रूप में करने से आप एप्लिकेशन को देशी कोड में संकलित कर सकते हैं, और C कोड के साथ इंटरैक्ट करने के लिए एक सरल इंटरफ़ेस मौजूदा उच्च-प्रदर्शन पुस्तकालयों का उपयोग करना आसान बनाता है।
  4. जंग क्रॉस-प्लेटफ़ॉर्म अनुप्रयोगों को विकसित करने पर केंद्रित है। कंपाइलर को आधिकारिक तौर पर विंडोज, लिनक्स और मैक ओएस एक्स प्लेटफॉर्म पर समर्थित किया गया है, और अन्य * एनआईएक्स प्लेटफॉर्म जैसे फ्रीबीएसडी पर पोर्ट हैं। कई प्रोसेसर आर्किटेक्चर भी समर्थित हैं: i386, x64 और ARM।
  5. जंग आपको विभिन्न शैलियों में लिखने की अनुमति देता है: वस्तु-उन्मुख, कार्यात्मक, अभिनेता-आधारित, अनिवार्य।
  6. Rust मौजूदा डीबगिंग टूल्स का समर्थन करता है: GDB, Valgrind, Instruments।


दर्शकों को लक्षित करें


सबसे पहले मैंने एक परिचयात्मक लेख लिखने की योजना बनाई, जो बहुत मूल बातें से भाषा की जांच करेगा, चर घोषणाओं के साथ शुरू होगा और मेमोरी मॉडल की कार्यक्षमता और सुविधाओं के साथ समाप्त होगा। एक ओर, इस तरह के दृष्टिकोण से सबसे अधिक संभव लक्षित दर्शकों तक पहुंचने की अनुमति होगी, दूसरी ओर, इसी तरह की सामग्री वाला एक लेख सी ++ या जावा जैसी भाषाओं के साथ काम करने वाले अच्छे अनुभव वाले लोगों के लिए निर्लिप्त होगा, और मुख्य विश्लेषण का गहन विश्लेषण नहीं होने देगा। जंग सुविधाएँ, अर्थात्, जो इसे आकर्षक बनाती है।

इसलिए, मैंने चर, लूप, फ़ंक्शंस, क्लोज़र, और अन्य सभी चीज़ों के बारे में विस्तार से वर्णन नहीं करने का फैसला किया जो कोड से स्पष्ट है। रस्ट की मुख्य विशेषताओं का विश्लेषण करने की प्रक्रिया में, पूरी तरह से स्पष्ट सुविधाओं के थोक को आवश्यक नहीं बताया जाएगा। नतीजतन, लेख भाषा की तीन मुख्य विशेषताओं में से दो के विवरण के लिए समर्पित है: स्मृति के साथ काम करना और समानांतर अनुप्रयोग लिखना सुरक्षित है। दुर्भाग्य से, लेखन के समय, नेटवर्क सबसिस्टम सक्रिय विकास में था, जिसने इसके साथ काम करने के विवरण को पूरी तरह से व्यर्थ में शामिल किया।

शब्दावली


द्वारा और बड़े, यह रूसी में रस्ट पर दो या तीन उपलब्ध लेखों में से एक है, इसलिए कोई स्थापित रूसी शब्दावली नहीं है और मुझे सबसे उपयुक्त समकक्षों को लेना होगा जो पहले से ही अन्य प्रोग्रामिंग भाषाओं में परिचित हैं। अंग्रेजी में आगे पढ़ने के प्रलेखन और लेखों की सुविधा के लिए, पहली बार एक रूसी शब्द दिखाई देता है, अंग्रेजी समकक्ष कोष्ठक में दिया गया है।

ज्यादातर समस्याएं बॉक्स और पॉइंटर के कारण हुईं। उनके गुणों के अनुसार, बॉक्स और पॉइंटर C ++ से सबसे स्मार्ट पॉइंटर्स की याद दिलाते हैं, इसलिए मैंने "पॉइंटर्स" शब्द का उपयोग करने का फैसला किया। इस प्रकार, स्वामित्व वाले बॉक्स अनूठे बिंदुओं में बदल गए, और अस्थायी बिंदुओं में उधार बिंदु।

स्मृति के साथ काम करें


मेमोरी के साथ काम करने के सिद्धांत रूस्त की प्रमुख विशेषताएं हैं जो इस भाषा को मेमोरी से पूर्ण अभिगम (जैसे C ++) और जीसी (जैसे जावा) से मेमोरी पर पूर्ण नियंत्रण रखने वाली भाषाओं से दोनों को अलग करती हैं। तथ्य यह है कि, एक तरफ, रस्ट डेवलपर को डेटा को नियंत्रित करने की क्षमता प्रदान करता है जहां डेटा को रखने के लिए, पॉइंटर्स के प्रकार द्वारा अलगाव का परिचय देना और संकलन चरण पर उनके उपयोग पर नियंत्रण प्रदान करना है। दूसरी ओर, लिंक गिनती तंत्र, जो भाषा के अंतिम संस्करण में एक पूर्ण जीसी द्वारा प्रतिस्थापित किया जाएगा, स्वचालित संसाधन प्रबंधन प्रदान करता है।

Rust में कई प्रकार के संकेत होते हैं जो विभिन्न प्रकार की मेमोरी में स्थित वस्तुओं को संबोधित करते हैं और विभिन्न नियमों का पालन करते हैं:

योजनाबद्ध रूप से, रस्ट मेमोरी मॉडल को निम्नानुसार दर्शाया जा सकता है:


ढेर का उपयोग


let x = Point {x: 1f, y: 1f}; // (1) let y = x; // (2) 

इसलिए, कोड (1) कार्य के ढेर पर टाइप प्वाइंट का एक ऑब्जेक्ट रखेगा, जिसमें इसे कहा जाएगा। ऐसे ऑब्जेक्ट (2) की प्रतिलिपि बनाते समय, ऑब्जेक्ट x के पॉइंटर को कॉपी नहीं किया जाएगा, लेकिन पॉइंट प्रकार की पूरी संरचना।

जानकारी के लिए: चर

जैसा कि आप ऊपर दिए गए उदाहरण से देख सकते हैं, Rust का उपयोग चरों को बनाने के लिए किया जाता है। डिफ़ॉल्ट रूप से, सभी चर स्थिर होते हैं और एक परिवर्तनशील चर बनाने के लिए म्यूट कीवर्ड जोड़ा जाना चाहिए। इस प्रकार, टाइप प्वाइंट का एक परिवर्तनशील परिवर्तनशील परिवर्तन इस तरह दिखाई दे सकता है: Let mut x = Point {x: 1f, y: 1f}}।

चर के साथ काम करते समय यह याद रखना बेहद जरूरी है कि यह वह डेटा है जो निरंतर रूप से बदल जाता है, और संकलक "चालबाजी" द्वारा उन्हें बदलने के प्रयासों की बारीकी से निगरानी करता है।
 let x = Point {x:1, y:2}; let y = Point {x:2, y:3}; let mut px = &x; // (1) let py = &y; px.x = 42; // (2) px = py; // (3) 

इसलिए, यह एक परिवर्तनशील चर बनाने के लिए (1) काफी संभव है जो निरंतर डेटा को इंगित करता है, लेकिन डेटा को बदलने के प्रयास (2) के परिणामस्वरूप संकलन चरण में एक त्रुटि होगी। लेकिन एक वैरिएबल का मान बदलना जो एक स्थिर प्वाइंट ऑब्जेक्ट के पते को संग्रहीत करता है और पहले बनाया गया वैध है (3)।
 error: assigning to immutable field px.x = 42; ^~~~~ 


साझा किए गए पॉइंटर्स

साझा पॉइंटर्स का उपयोग स्थानीय कार्य ढेर में स्थित वस्तुओं के लिए पॉइंटर्स के रूप में किया जाता है। प्रत्येक कार्य का अपना स्थानीय ढेर होता है, और इसमें स्थित वस्तुओं की ओर संकेत कभी भी इसकी सीमाओं के बाहर पारित नहीं किया जा सकता है। साझा पॉइंटर्स बनाने के लिए, यूनीरी ऑपरेटर @ का उपयोग करें
 let x = @Point {x: 1f, y: 1f}; 

स्टैक ऑब्जेक्ट्स के विपरीत, कॉपी करते समय, केवल पॉइंटर को कॉपी किया जाता है, डेटा को नहीं। यह इस संपत्ति से है कि इस प्रकार के पॉइंटर्स का नाम चला गया, क्योंकि उनका व्यवहार C ++ भाषा से share_ptr के समान है।
 let y = x; //  x  y     //     Point 

इस तथ्य पर ध्यान देना भी आवश्यक है कि अपने स्वयं के प्रकार के लिए एक सूचक युक्त संरचना बनाना असंभव है (एक क्लासिक उदाहरण एक बस जुड़ा हुआ सूची है)। कंपाइलर को इस तरह के निर्माण की अनुमति देने के लिए, ऑप्शन (1) प्रकार में पॉइंटर को लपेटना आवश्यक है।
 struct LinkedList<T> { data: T, nextNode: Option<@LinkedList<T>> // (1) } 


अद्वितीय संकेत

साझा बिंदुओं की तरह अद्वितीय संकेत, ढेर पर वस्तुओं के संकेत हैं, जो कि जहां उनकी समानता समाप्त होती है। अद्वितीय बिंदुओं द्वारा संबोधित डेटा एक्सचेंज के ढेर पर स्थित है, जो सभी कार्यों के लिए सामान्य है। अद्वितीय पॉइंटर्स बनाने के लिए, यूनिरी ऑपरेटर ~ का उपयोग करें
 let p = ~Point {x: 1f, y: 1f}; 

अद्वितीय संकेत स्वामित्व के शब्दार्थ को लागू करते हैं, इसलिए एक वस्तु केवल एक अद्वितीय सूचक को संबोधित कर सकती है। C ++ डेवलपर्स को अद्वितीय Rust पॉइंटर्स और STL से अनूठे_ptr वर्ग के बीच समानताएं मिलने की संभावना है।
 let new_p = p; // (1) let val_x = px; // (2) 

पॉइंटर p_ के पॉइंटर new_p को असाइन करना इस तथ्य की ओर जाता है कि new_p पहले बनाए गए प्रकार प्वाइंट के ऑब्जेक्ट को इंगित करना शुरू कर देता है, और पॉइंटर पी अनिनिर्धारित है। डी-इनिशियलाइज्ड वेरिएबल्स (2) के साथ काम करने के प्रयास के मामले में, कंपाइलर स्थानांतरित मूल्य त्रुटि का उपयोग उत्पन्न करता है और मूल के बाद के डी-इनिशियलाइज़ेशन के साथ पॉइंटर असाइन करने के बजाय चर की एक प्रति बनाने का सुझाव देता है।
 let p = ~Point {x: 1f, y: 1f}; let new_p = p.clone(); // (1) 

प्रतिलिपि के स्पष्ट निर्माण के लिए धन्यवाद (1), पहले बनाए गए प्रकार बिंदु के ऑब्जेक्ट की एक प्रति के लिए new_p अंक, और सूचक p नहीं बदलता है। बिंदु संरचना पर लागू करने के लिए क्लोन विधि के लिए, संरचना को # [व्युत्पन्न (क्लोन)] विशेषता का उपयोग करके घोषित किया जाना चाहिए।
 #[deriving(Clone)] struct Point {x: float, y: float} 


अस्थायी संकेत

अस्थायी संकेत - संकेत जो किसी भी प्रकार की स्मृति में स्थित किसी वस्तु को इंगित कर सकते हैं: स्टैक, स्थानीय या ढेर विनिमय, साथ ही साथ किसी भी डेटा संरचना के आंतरिक सदस्य। भौतिक स्तर पर, अस्थायी संकेत विशिष्ट सी पॉइंटर्स हैं और परिणामस्वरूप, कचरा कलेक्टर द्वारा निगरानी नहीं की जाती है और किसी भी अतिरिक्त ओवरहेड को लाइक नहीं करते हैं। इसी समय, सी पॉइंटर्स से उनका मुख्य अंतर सुरक्षित उपयोग की संभावना की गारंटी के लिए संकलन चरण में किए गए अतिरिक्त चेक हैं। अस्थायी पॉइंटर्स बनाने के लिए, अपर ऑपरेटर और का उपयोग करें
 let on_the_stack = &Point {x: 3.0, y: 4.0}; // (1) 

स्टैक पर टाइप प्वाइंट का एक ऑब्जेक्ट (1) बनाया गया था और अस्थायी सूचक on_the_stack में संग्रहीत किया गया था। यह कोड निम्नलिखित के समान है:
 let on_the_stack = Point {x: 3.0, y: 4.0}; let on_the_stack_pointer = &on_the_stack; 

स्टैक प्रकार के अलावा अन्य प्रकार अस्थायी पॉइंटर्स में स्वचालित रूप से परिवर्तित हो जाते हैं, बिना पते के ऑपरेटर का उपयोग किए बिना, जिससे फ़ंक्शन (1) लिखना आसान हो जाता है यदि पॉइंटर का प्रकार कोई फर्क नहीं पड़ता।
 let on_the_stack : Point = Point {x: 3.0, y: 4.0}; let managed_box : @Point = @Point {x: 5.0, y: 1.0}; let owned_box : ~Point = ~Point {x: 7.0, y: 9.0}; fn compute_distance(p1: &Point, p2: &Point) -> float { // (1) let x_d = p1.x - p2.x; let y_d = p1.y - p2.y; sqrt(x_d * x_d + y_d * y_d) } compute_distance(&on_the_stack, managed_box); compute_distance(managed_box, owned_box); 

और अब आप डेटा संरचना के आंतरिक तत्व के लिए एक अस्थायी सूचक कैसे प्राप्त कर सकते हैं, इसका एक छोटा उदाहरण।
 let y = &point.y; 

समय बिंदुओं के जीवनकाल की निगरानी करना एक बहुत ही स्वछंद और बहुत स्थापित विषय नहीं है। आप चाहें तो इसे Rust Borrowed Pointers Tutorial और Lifetime Notation नामक लेख में विस्तार से पढ़ सकते हैं।

Dereferencing पॉइंटर्स

पॉइंटर्स का उपयोग करके संबोधित किए गए मानों तक पहुंचने के लिए, आपको डेरेफ़रिंग पॉइंटर्स ऑपरेशन करना होगा। संरचित वस्तुओं के क्षेत्रों तक पहुँचते समय, स्वचालित रूप से डीफ़ेरिंग की जाती है।
 let managed = @10; let owned = ~20; let borrowed = &30; let sum = *managed + *owned + *borrowed; 


पॉइंटर्स के बीच कनवर्ट करें

रुस्त के साथ काम शुरू करने के लगभग तुरंत बाद, यह सवाल उठता है: "किसी ऑब्जेक्ट को एक यूनीक पॉइंटर का उपयोग करके किसी साझा या इसके विपरीत में कनवर्ट करने के लिए कैसे?" इस सवाल का जवाब छोटा है और पहले कुछ हद तक हतोत्साहित करने वाला: कोई रास्ता नहीं। यदि आप इसके बारे में अच्छी तरह से सोचते हैं, तो यह स्पष्ट हो जाता है कि इस तरह के रूपांतरण के कोई भी साधन नहीं हैं और न ही हो सकते हैं, क्योंकि ऑब्जेक्ट अलग-अलग प्रकार के होते हैं और अलग-अलग नियमों का पालन करते हैं, ऑब्जेक्ट्स पर निर्भरता ग्राफ़ हो सकते हैं, जो स्वचालित रूप से ट्रैक करना भी मुश्किल है। इसलिए, यदि आपको पॉइंटर्स के बीच कनवर्ट करने की आवश्यकता है, जो कि ढेर के बीच चलती वस्तुओं से ज्यादा कुछ नहीं है, तो आपको वस्तुओं की प्रतियां बनाने की आवश्यकता है, जिसके लिए आप क्रमांकन का उपयोग कर सकते हैं।

कार्य


रस्ट की दूसरी प्रमुख विशेषता समानांतर अनुप्रयोग लिख रही है। समवर्ती अनुप्रयोगों को लिखने के संदर्भ में, रस्ट अपने अभिनेता मॉडल और उनके और लिम्बो के बीच संदेश के साथ एर्लांग से मिलता-जुलता है। इस मामले में, डेवलपर को यह चुनने का अवसर दिया जाता है कि वह संदेश भेजते समय मेमोरी को कॉपी करना चाहता है या वस्तु का स्वामित्व स्थानांतरित करना चाहता है। और जब कई कार्य एक ही वस्तु के साथ मिलकर काम करते हैं, तो आप आसानी से एक-लेखक-कई-पाठकों के लिए पहुंच को व्यवस्थित कर सकते हैं। निर्मित कार्यों के लिए सबसे उपयुक्त अनुसूचक चुनने या अपना खुद का लिखने का अवसर है।

जानकारी के लिए: do-syntax

कार्यों के साथ काम करने के विवरण के लिए आगे बढ़ने से पहले, अपने आप को डो-सिंटैक्स के साथ परिचित करना उचित है जो रस्ट उच्च-क्रम के कार्यों के साथ काम को सरल बनाने के लिए उपयोग करता है। एक उदाहरण के रूप में, हम प्रत्येक फ़ंक्शन को ऑप्टर के प्रत्येक तत्वों को पॉइंटर (1) से पास करते हुए ऑप फंक्शन में ले जा सकते हैं।
 fn each(v: &[int], op: &fn(v: &int)) { let mut n = 0; while n < v.len() { op(&v[n]); // (1) n += 1; } } 

प्रत्येक फ़ंक्शन का उपयोग करते हुए, do-syntax (1) का उपयोग करके, आप सरणी के प्रत्येक तत्वों को प्रदर्शित कर सकते हैं, यह भूलकर नहीं कि लैम्ब्डा को कोई मान पारित नहीं किया जाएगा, लेकिन डेटा को एक्सेस करने के लिए एक पॉइंटर जिसे dereferenced (2) होना चाहिए:
 do each([1, 2, 3]) |n| { // (1) io::println(n.to_str()); // (2) } 

चूंकि सिंटैक्स में वाक्य रचना चीनी है, इसलिए नीचे का अंकन सिंटैक्स का उपयोग करते हुए संकेतन के बराबर है।
 each([1, 2, 3], |n| { io::println(n.to_str()); }); 


किसी कार्य को चलाना

रस्ट में किसी कार्य को बनाना और निष्पादित करना बहुत सरल है। कार्यों के साथ काम करने से संबंधित कोड std :: कार्य मॉड्यूल में केंद्रित है, और कार्य बनाने और शुरू करने का सबसे सरल तरीका इस मॉड्यूल से स्पॉन फ़ंक्शन को कॉल करना है।
 use std::task; fn print_message() { println("Message form task 1"); } fn main() { spawn(print_message); // (1) spawn( || println("Message form task 2") ); // (2) do spawn { // (3) println("Message form task 3"); } } 

स्पॉन फ़ंक्शन एक तर्क के रूप में एक क्लोजर लेता है और इसे एक कार्य के रूप में लॉन्च करता है (यह मत भूलो कि रस्ट में कार्यों को हरे धागे के शीर्ष पर लागू किया जाता है)। वर्तमान कार्य को प्राप्त करने के लिए जिसके भीतर कोड निष्पादित होता है, आप कार्य मॉड्यूल से get_task () विधि का उपयोग कर सकते हैं। यह देखते हुए कि कार्य के ढांचे के भीतर क्लोजर किए जाते हैं, कार्य को शुरू करने के लिए 3 तरीकों को ग्रहण करना मुश्किल नहीं है: फ़ंक्शन का पता (1) पास करके, क्लोजर "जगह" (2) बनाकर या, जो भाषा की विचारधारा के दृष्टिकोण से अधिक सच है, का उपयोग करते हुए वाक्यविन्यास (3)।

कार्यों के बीच सहभागिता

रस्ट मेमोरी मॉडल, सामान्य स्थिति में, विभिन्न कार्यों (साझा मेमोरी मॉडल) से समान मेमोरी तक संयुक्त पहुंच की अनुमति नहीं देता है, इसके बजाय कार्यों (मेलबॉक्स मॉडल) के बीच संदेश विनिमय की पेशकश की जाती है। एक ही समय में, कई कार्यों के लिए, रीड-ओनली और मल्टी-रीडर मोड में साझा मेमोरी के साथ काम करना संभव है। कार्यों के बीच बातचीत को व्यवस्थित करने के लिए, रूस्ट निम्नलिखित तरीके प्रदान करता है:


निम्न स्तर का संदेश

इस समय कार्यों के बीच बातचीत का सबसे व्यापक रूप से उपयोग किया जाने वाला तरीका है :: कॉम मॉड्यूल। Std :: comm से कोड अच्छी तरह से डीबग किया गया है, अच्छी तरह से प्रलेखित है, और उपयोग करने के लिए काफी आसान है। एसटीडी :: कॉम मैसेजिंग इंजन का आधार थ्रेड्स हैं, जिन्हें चैनलों और बंदरगाहों के माध्यम से हेरफेर किया जाता है। एक स्ट्रीम एक यूनिडायरेक्शनल कम्युनिकेशन मेकेनिज्म है जिसमें एक पोर्ट का उपयोग संदेश भेजने के लिए किया जाता है, और एक चैनल का उपयोग भेजी गई सूचनाओं को प्राप्त करने के लिए किया जाता है। एक धारा का उपयोग करने का सबसे सरल उदाहरण इस प्रकार है:
 let (chan, port) = stream(); // (1) port.send("data"); // (2) // port.send(1); // (3) println(chan.recv()); // (4) 

इस उदाहरण में, एक जोड़ी (1) बनाई जाती है, जिसमें एक चैनल और एक पोर्ट होता है, जो एक स्ट्रिंग डेटा प्रकार भेजने के लिए उपयोग किया जाता है। स्ट्रीम () फ़ंक्शन के प्रोटोटाइप पर विशेष ध्यान दिया जाना चाहिए, जो इस तरह दिखता है: fn स्ट्रीम <T: Send> () -> (पोर्ट, चान)। जैसा कि प्रोटोटाइप से देखा जा सकता है, चैनल और पोर्ट टेम्पलेट प्रकार हैं, जो पहली नज़र में, ऊपर दिए गए कोड से स्पष्ट नहीं हैं। इस मामले में, स्थानांतरित किए गए डेटा का प्रकार पहले उपयोग के आधार पर स्वचालित रूप से प्रदर्शित होता है। इसलिए, यदि आप एक लाइन को यूनिट (3) को स्ट्रीम पर भेजते हैं, तो कंपाइलर एक त्रुटि संदेश देगा:
 error: mismatched types: expected `&'static str` but found `<VI0>` (expected &'static str but found integral variable 

भेजें पैरामीटर वर्ग पर विशेष ध्यान दिया जाता है, जिसका अर्थ है कि केवल उन वस्तुओं को जो वर्तमान कार्य के बाहर स्थानांतरण का समर्थन करते हैं, एक धारा का उपयोग करके स्थानांतरित किया जा सकता है।

स्ट्रीम से डेटा प्राप्त करने के लिए, आप recv () फ़ंक्शन का उपयोग कर सकते हैं, जो या तो डेटा को लौटा देगा या कार्य को प्रकट होने तक रोक देगा। ऊपर के उदाहरण को देखते हुए, इसमें संदेह कम होता है कि यह पूरी तरह से बेकार है, क्योंकि एक कार्य के ढांचे के भीतर थ्रेड का उपयोग करके संदेश भेजने में कोई व्यावहारिक अर्थ नहीं है। इसलिए यह अधिक व्यावहारिक चीजों पर जाने लायक है, जैसे कि कार्यों के बीच जानकारी स्थानांतरित करने के लिए थ्रेड्स का उपयोग करना।
 let value = vec::from_fn(5, |x| x + 1); // (1) let (server_chan, server_port) = stream(); // (2) let (client_chan, client_port) = stream(); // (3) do task::spawn { let val: ~[uint] = server_chan.recv(); // (4) let res = val.map(|v| {v+1}); client_port.send(res) // (5) } server_port.send(value); // (6) io::println(fmt!("Result: %?", client_chan.recv())); // (7) 

धाराओं के साथ काम करते समय आपको सबसे पहले ध्यान देना चाहिए कि अद्वितीय बिंदुओं द्वारा संबोधित मूल्यों को पारित करने की आवश्यकता है, और from_fn () (1) फ़ंक्शन केवल इस तरह की एक सरणी बनाता है। चूंकि स्ट्रीम यूनिडायरेक्शनल है, रिक्वेस्ट (2) ट्रांसमिट करने और रिस्पॉन्स (3) प्राप्त करने के लिए दो स्ट्रीम की जरूरत होगी। रिकव () फ़ंक्शन का उपयोग करके, डेटा को स्ट्रीम (4) से पढ़ा जाता है, और यदि कोई भी उपलब्ध नहीं है, तो स्ट्रीम तब तक कार्य को अवरुद्ध कर देगा जब तक यह प्रकट नहीं होता है। क्लाइंट को परिणाम भेजने के लिए, भेजें () (5) फ़ंक्शन का उपयोग करें, जो सर्वर से नहीं, बल्कि क्लाइंट स्ट्रीम से संबंधित है; उसी तरह से सर्वर टास्क को भेजने के लिए डेटा से निपटना आवश्यक है: सर्वर पोर्ट से संबंधित सेंड () फ़ंक्शन का उपयोग करके उन्हें (6) लिखा जाता है। बहुत अंत में, सर्वर टास्क द्वारा प्रसारित परिणाम क्लाइंट स्ट्रीम से पढ़ा जाता है (7)।

इस प्रकार, सर्वर पर संदेश भेजने और सर्वर साइड पर संदेश प्राप्त करने के लिए, server_chan, server_port स्ट्रीम का उपयोग किया जाता है। धारा की यूनिडायरेक्शनलिटी के कारण, सर्वर गणना का परिणाम प्राप्त करने के लिए एक क्लाइंट स्ट्रीम बनाई गई, जिसमें क्लाइंट_चैन, क्लाइंट_पोर्ट की एक जोड़ी शामिल है।

स्ट्रीम साझाकरण

हालाँकि, स्ट्रीम एक यूनिडायरेक्शनल डेटा ट्रांसफर मैकेनिज़्म है, लेकिन यह उन सभी में से प्रत्येक के लिए एक नई स्ट्रीम बनाने की आवश्यकता नहीं है, जो डेटा भेजना चाहते हैं, क्योंकि एक ऐसा सिस्टम है जो "वन-रिसीवर-कई-सेंडर" मोड में ऑपरेशन सुनिश्चित करता है।
 enum command { // (1) print_hello(int), stop } ... let (server_chan, server_port) = stream(); // (2) let (client_chan, client_port) = stream(); // (3) do spawn { // (4) let mut hello_count = 0; let mut done = false; while !done { let req: command = server_chan.recv(); // (5) match req { print_hello(client_id) => { println( fmt!("Hello from client #%d", client_id)); hello_count += 1; } stop => { println("Stop command received"); done = true; } } } client_port.send(hello_count); // (6) } let server_port = SharedChan::new(server_port); // (7) for i in range(0, 5) { let server_port = server_port.clone(); // (8) do spawn { server_port.send(print_hello(i)); // (9) } } server_port.send(stop); println(fmt!("Result: %?", client_chan.recv())); 

इसके लिए, साथ ही "एक-पाठक-एक-लेखक" योजना के लिए, सर्वर (2) और क्लाइंट (3) थ्रेड बनाना और सर्वर कार्य (3) चलाना आवश्यक है। सर्वर कार्य का तर्क अत्यंत सरल है: क्लाइंट (9) द्वारा प्रेषित सर्वर चैनल से डेटा (5) पढ़ा जाता है, स्क्रीन पर अनुरोध की प्राप्ति के बारे में एक संदेश प्रदर्शित करता है और क्लाइंट स्ट्रीम को प्राप्त प्रिंट_हेल्लो (5) अनुरोधों की परिणामी संख्या भेजता है। चूँकि कई लेखक हैं, इसलिए चान के बजाए इसे SharedChan में परिवर्तित करके (7) सर्वर पोर्ट के प्रकार में परिवर्तन करना आवश्यक है, और क्लोन () पद्धति का उपयोग करके प्रत्येक लेखक के लिए पोर्ट (8) की एक अद्वितीय प्रतिलिपि बनाएँ। पोर्ट के साथ आगे का काम पिछले उदाहरण से अलग नहीं है: सर्वर (9) को केवल अंतर के साथ डेटा भेजने के लिए सेंड () विधि का उपयोग किया जाता है कि अब डेटा को एक साथ कई कार्यों से भेजा जाता है।

एक धारा के साथ काम करने के तरीके को दर्शाने के अलावा, यह उदाहरण दिखाता है कि एक धारा का उपयोग करके कई अलग-अलग प्रकार के संदेश कैसे भेजें। चूंकि स्ट्रीम द्वारा प्रेषित डेटा का प्रकार संकलन चरण में सेट किया गया है, इसलिए विभिन्न प्रकारों के डेटा को स्थानांतरित करने के लिए, आपको बाइनरी डेटा के हस्तांतरण के बाद या तो क्रमांकन का उपयोग करना होगा (यह विधि अनुभाग "ऑब्जेक्ट्स भेज") या स्थानांतरण एन्यूमरेशन (1) में नीचे वर्णित है। उनके गुणों के आधार पर, रस्ट एनुमरेशंस सी भाषा या वैरिएंट प्रकार के संघों के समान हैं, जो लगभग सभी उच्च-स्तरीय प्रोग्रामिंग भाषाओं में एक रूप या किसी अन्य में मौजूद है।

शिपिंग ऑब्जेक्ट्स

ऐसे मामलों में जहां अद्वितीय संकेत द्वारा संबोधित मूल्यों को अग्रेषित करने की आवश्यकता एक समस्या बन जाती है, फ्लैटपाइप्स मॉड्यूल बचाव के लिए आता है। यह मॉड्यूल आपको सरणी या ऑब्जेक्ट के रूप में किसी भी बाइनरी डेटा को भेजने और प्राप्त करने की अनुमति देता है जो क्रमांकन का समर्थन करते हैं।
 #[deriving(Decodable)] // (1) #[deriving(Encodable)] // (2) struct EncTest { val1: uint, val2: @str, val3: ~str } ... let (server_chan, server_port) = flatpipes::serial::pipe_stream(); // (3) do task::spawn { let value = @EncTest{val1: 1u, val2: @"test string 1", val3: ~"test string 2"}; server_port.send(value); // (4) } let val = server_chan.recv(); server_port.send(value); // (5) 

जैसा कि आप उदाहरण से देख सकते हैं, फ्लैटपाइप के साथ काम करना बेहद सरल है। एक संरचना जिसकी वस्तुओं को फ्लैटपाइप के माध्यम से प्रेषित किया जाएगा, को क्रमबद्ध (1) और डीसेरिएबल (2) घोषित किया जाना चाहिए। फ्लैटपाइप्स (3) बनाना तकनीकी रूप से नियमित स्ट्रीम बनाने से अलग नहीं है, जैसे (4) प्राप्त करना और चैनल (पोर्ट) का उपयोग करके संदेश भेजना। फ्लैटपाइप और धारा के बीच मुख्य अंतर भेजने की तरफ वस्तु की एक गहरी प्रतिलिपि का निर्माण है और प्राप्त पक्ष पर एक नई वस्तु का निर्माण है। इस दृष्टिकोण के लिए धन्यवाद, नियमित धाराओं की तुलना में फ्लैटपाइप्स के साथ काम करने का ओवरहेड बढ़ता है, लेकिन कार्यों के बीच डेटा स्थानांतरित करने की क्षमता बढ़ जाती है।

उच्च स्तरीय संदेश अमूर्तता

ऊपर के अधिकांश उदाहरणों में, दो धाराएँ बनाई गई हैं: एक सर्वर से डेटा भेजने के लिए, दूसरी सर्वर से डेटा प्राप्त करने के लिए। यह दृष्टिकोण किसी भी ठोस लाभ को नहीं लाता है और केवल कोड को लिट करता है। इस संबंध में, मॉड्यूल अतिरिक्त :: कॉम बनाया गया था, जो std :: comm पर एक उच्च-स्तरीय अमूर्त है और इसमें डुप्लेक्सस्ट्रीम शामिल है, जो एकल स्ट्रीम के भीतर द्विदिश संचार के लिए अनुमति देता है। बेशक, यदि आप डुप्लेक्सस्ट्रीम के स्रोत कोड को देखते हैं, तो यह स्पष्ट हो जाता है कि यह मानक धाराओं की एक जोड़ी पर एक सुविधाजनक ऐड-ऑन से ज्यादा कुछ नहीं है।
 let value = ~[1, 2, 3, 4, 5]; let (server, client) = DuplexStream(); // (1) do task::spawn { let val: ~[uint] = server.recv(); // (2) io::println(fmt!("Value: %?", val)); let res = val.map(|v| {v+1}); server.send(res) // (3) } client.send(value); // (4) io::println(fmt!("Result: %?", client.recv())); // (5) 

डुप्लेक्सस्ट्रीम के साथ काम करते समय, (1) दो द्विदिश धाराओं की एक जोड़ी बनाई जाती है, जिनमें से संदेश भेजने और प्राप्त करने के लिए दोनों का उपयोग किया जा सकता है। सर्वर ऑब्जेक्ट को कार्य के संदर्भ द्वारा कैप्चर किया जाता है और सर्वर कार्य में (2) और भेजने (3) संदेश, और क्लाइंट कार्य (4,5) में क्लाइंट ऑब्जेक्ट का उपयोग किया जाता है। डुप्लेक्सस्ट्रीम के साथ काम करने का सिद्धांत नियमित धाराओं के साथ काम करने से अलग नहीं है, लेकिन यह आपको सहायक वस्तुओं की संख्या को कम करने की अनुमति देता है।

आर्क मॉड्यूल

संदेश भेजने के सभी आकर्षण के बावजूद, जल्दी या बाद में सवाल उठता है: "एक बड़ी डेटा संरचना के साथ क्या करना है, जिसकी पहुंच एक ही समय में कई कार्यों से आवश्यक है?" अनुप्रयोग, और इसका रखरखाव एक वास्तविक दुःस्वप्न में बदल जाएगा। यह ऐसे मामलों के लिए है जो आर्क मॉड्यूल बनाया गया था, जो कई कार्यों से एक ही वस्तु तक संयुक्त पहुंच को व्यवस्थित करने की अनुमति देता है।
केवल पढ़ने के लिए अद्वितीय संकेत साझा करना

पहले आपको सबसे सरल मामले से निपटने की आवश्यकता है - कई कार्यों से अपरिवर्तनीय डेटा तक पहुंच साझा करना।इस समस्या को हल करने के लिए, आपको आर्क मॉड्यूल का उपयोग करने की आवश्यकता है, जो किसी साझा ऑब्जेक्ट के लिए स्वचालित रूप से लिंक (एटोमिकली रेफरेंस-काउंटर) के लिए एक तंत्र लागू करता है। एआरसी ऑब्जेक्ट बनाने के लिए फ़ंक्शन के प्रोटोटाइप में, पब fn नया (डेटा: टी) -> आर्क, आपको टाइप टी पर लगाए गए प्रतिबंधों पर ध्यान देना चाहिए।
 impl<T:Freeze+Send> Arc<T> { pub fn new(data: T) -> Arc<T> { ... } ... } 

अब ऑब्जेक्ट न केवल सेंड क्लास का होना चाहिए, जैसा कि स्ट्रीम के मामले में था, बल्कि फ्रीज क्लास के लिए भी था, जो टी ऑब्जेक्ट के अंदर उत्परिवर्तित फ़ील्ड्स या पॉइंटर्स को म्यूटेबल फ़ील्ड्स की अनुपस्थिति की गारंटी देता है (जैसे कि रस्ट में ऑब्जेक्ट्स कहा जाता है गहरी अपरिवर्तनीय वस्तुएं)।
 let data = arc::Arc::new(~[1, 2, 3, 4, 5]); // (1) let shared_data = data.clone(); // (2) do spawn { let val = shared_data.get(); // (3) println(fmt!("Shared array: %?", val)); } println(fmt!("Original array: %?", data.get())); // (4) 

मान लीजिए कि इस उदाहरण में धाराओं के साथ कोई काम नहीं है, लेकिन आर्क के साथ काम करना स्पष्ट करने के लिए काफी पर्याप्त है, क्योंकि यह स्पष्ट रूप से इस मॉड्यूल की मुख्य कार्यक्षमता को प्रदर्शित करता है - साथ ही विभिन्न कार्यों से एक ही डेटा तक पहुंचने की क्षमता। तो, आर्क (1) में लिपटे एक ही सरणी को साझा करने के लिए, आपको रैपर (2) का आर्क क्लोन बनाने की आवश्यकता है, जिससे नए (3) और मुख्य (4) दोनों कार्यों से डेटा तक पहुंच संभव हो जाएगी।

अद्वितीय संकेत करने के लिए आर / डब्ल्यू का उपयोग

RWArc मॉड्यूल मुझ में दोहरी भावनाएं पैदा करता है। एक ओर, RWArc के लिए धन्यवाद, "कई पाठक एक लेखक" की अवधारणा को लागू करना संभव है, जो कि व्यापक रूप से जाना जाता है और ज्यादातर डेवलपर्स के लिए जाना जाता है, जो कि संभवतः अच्छा है, क्योंकि अवधारणा व्यापक रूप से जानी जाती है। दूसरी ओर, मेमोरी तक साझा पहुंच, आरओ एक्सेस नहीं, जिसे थोड़ा पहले बताया गया था, लेकिन आरडब्ल्यू एक्सेस, गतिरोध के साथ समस्याओं से भरा हुआ है, जिसमें से रस्ट को डेवलपर्स की रक्षा करनी चाहिए। खुद के लिए, मैं निम्नलिखित निष्कर्ष पर आया: आपको मॉड्यूल के बारे में जानने की आवश्यकता है, लेकिन आपको इसका उपयोग तब तक नहीं करना चाहिए जब तक कि बिल्कुल आवश्यक न हो।
 let data = arc::RWArc::new(~[1, 2, 3, 4, 5]); // (1) do 5.times { let reader = data.clone(); // (2) do spawn { do reader.read() |data| { // (3) io::println(fmt!("Value: %?", data)); // (4) } } } do spawn { do data.write() |data| { // (5) for x in data.mut_iter() { *x = *x * 2 } // (6) } } 

उपरोक्त उदाहरण में, एक (1) सरणी बनाई गई है, RWArc में लपेटी गई है, ताकि इसे पढ़ने (4) और लेखन (6) दोनों के लिए एक्सेस किया जा सके। RWArc उदाहरण और पिछले सभी उदाहरणों के बीच मूलभूत अंतर रीड () (3) और राइट () (5) कार्यों को एक तर्क के रूप में बंद करने का उपयोग है। RWArc में लिपटे डेटा को पढ़ना और लिखना केवल इन कार्यों में किया जा सकता है। और, हमेशा की तरह, आपको सर्किट से इसे एक्सेस करने के लिए ऑब्जेक्ट की एक प्रति (2) बनाने की आवश्यकता है, अन्यथा मूल अप्राप्य हो जाएगा।

यह कैसे भी संभव है?

हां, यह वास्तव में सवाल है जो आपके सीखने के बाद उठता है कि आर्क और RWArc मॉड्यूल रस्ट में मौजूद हैं। पहली नज़र में, वे आम तौर पर रस्ट में मेमोरी के साथ काम करने की अवधारणा का खंडन करते हैं, और विशेष रूप से अद्वितीय संकेत के सिद्धांत। इस भाषा के निर्माता या डेवलपर नहीं होने के नाते, मैं केवल इस बारे में बात कर सकता हूं कि इस तरह का व्यवहार कैसे संभव है। रस्ट भाषा के भाग के रूप में, असुरक्षित कीवर्ड है, जो आपको कोड लिखने की अनुमति देता है जो सीधे मेमोरी के साथ काम करता है, कॉल फ़ंक्शंस जो मेमोरी प्रबंधन के संदर्भ में असुरक्षित हैं, जैसे कि मॉलोक, फ्री और एड्रेस अंकगणित का उपयोग करें। इस सुविधा का उपयोग अंतर्निहित रस्ट मेमोरी सुरक्षा को बायपास करने और उसी वस्तु तक साझाकरण पहुंच प्रदान करने के लिए किया जाता है। इस कार्यक्षमता से संबंधित सभी कोडइसे "COMPLETELY UNSAFE" के रूप में चिह्नित किया गया है और इसे सीधे अंतिम उपयोगकर्ताओं द्वारा उपयोग नहीं किया जाना चाहिए।

एक निष्कर्ष के बजाय


हालाँकि अभी औद्योगिक उपयोग के लिए जंग उपयुक्त नहीं है, मेरी राय में, इसमें बहुत संभावनाएँ हैं। यह अच्छी तरह से हो सकता है कि कुछ वर्षों में रस्ट सी और सी ++ जैसी अद्भुत डायनासोर भाषाओं के साथ प्रतिस्पर्धा करने में सक्षम होगा, कम से कम लेखन नेटवर्क और समानांतर अनुप्रयोगों से संबंधित क्षेत्रों में। एक अंतिम उपाय के रूप में, मैं वास्तव में ऐसी उम्मीद करता हूं।

लेख के लिए, इसे समाप्त मान लेना सबसे अधिक असंभव है: सबसे पहले, भाषा वाक्य रचना शायद कई परिवर्तनों से गुजरना होगा, और दूसरी बात, भाषा की प्रमुख विशेषताओं में से तीसरे पर काम पूरा होना चाहिए - नेटवर्क इंटरैक्शन के लिए समर्थन। जैसे ही यह कार्यक्षमता अधिक या कम पूर्ण स्थिति में आती है, मैं इसके बारे में लिखूंगा।

Source: https://habr.com/ru/post/In191916/


All Articles