Rust में कॉलबैक: दर्द से लेकर मुहावरों के माध्यम से सुंदरता तक

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

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

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

परिचय: Rust में कॉलबैक एक चुनौती (और अवसर) क्यों हैं

\n

यदि आपने कभी JavaScript, Python या C# में लिखा है, तो कॉलबैक आपके लिए एक परिचित उपकरण है। आप एक फ़ंक्शन पास करते हैं जिसे बाद में कॉल किया जाएगा, और शांति से रहते हैं। लेकिन जब आप Rust में आते हैं, तो यह सरल पैटर्न अचानक एक खदान में बदल जाता है: स्वामित्व, उधार, जीवनकाल, dyn बनाम impl...

\n

Stack Overflow पर प्रश्न "Idiomatic callbacks in Rust" ने हजारों व्यूज एकत्र किए हैं। इसका मतलब है कि अनुभवी डेवलपर्स भी इस विषय पर ठोकर खाते हैं। लेकिन अच्छी खबर यह है: Rust कॉलबैक को लागू करने के लिए एक नहीं, बल्कि कई सुंदर तरीके प्रदान करता है। और आज हम उन सभी को समझेंगे - सरल क्लोजर से लेकर शक्तिशाली ट्रेट-ऑब्जेक्ट और एनम-पैटर्न तक।

\n

यह लेख आपके लिए है, यदि आप:

\n
    \n
  • Rust सीखना शुरू किया है और यह समझना चाहते हैं कि एसिंक्रोनस और इवेंट-संचालित आर्किटेक्चर को सही तरीके से कैसे व्यवस्थित किया जाए;
  • \n
  • अन्य भाषाओं से आ रहे हैं और परिचित पैटर्न के मुहावरेदार एनालॉग ढूंढ रहे हैं;
  • \n
  • Rust में स्वच्छ, सुरक्षित और कुशल कोड लिखना चाहते हैं।
  • \n
\n

चलिए शुरू करते हैं!

\n\n

समस्या: सरल कॉलबैक में क्या गलत है?

\n

कल्पना करें कि हम एक सरल टास्क शेड्यूलर लिख रहे हैं। Python में यह इस तरह दिखेगा:

\n
def schedule(callback):\n    # कुछ करो\n    callback(result)
\n

Rust में पहला प्रयास इस प्रकार हो सकता है:

\n
fn schedule(callback: fn(i32) -> i32) {\n    let result = 42;\n    callback(result);\n}
\n

और यह काम करता है! लेकिन केवल फ़ंक्शन के लिए, क्लोजर के लिए नहीं। एक क्लोजर पास करने का प्रयास करें जो एक वेरिएबल को कैप्चर करता है:

\n
let factor = 2;\nlet closure = |x| x * factor;\nschedule(closure); // त्रुटि! expected fn pointer, found closure
\n

कंपाइलर सही रूप से शिकायत करता है: fn एक फ़ंक्शन पॉइंटर है, जबकि क्लोजर एक अनाम प्रकार है जो परिवेश को कैप्चर कर सकता है। हमें अधिक लचीले दृष्टिकोण की आवश्यकता है।

\n\n

दृष्टिकोण 1: impl Fn के साथ क्लोजर (सरलता और लचीलापन)

\n

सबसे सरल और अक्सर उपयोग किया जाने वाला तरीका Fn, FnMut या FnOnce ट्रेट के साथ एक जेनेरिक प्रकार का उपयोग करना है।

\n

Fn, FnMut, FnOnce का उपयोग कब करें?

\n
    \n
  • FnOnce - यदि कॉलबैक ठीक एक बार कॉल किया जाता है (कैप्चर किए गए मानों का उपभोग करता है);
  • \n
  • FnMut - यदि कॉलबैक कैप्चर की गई स्थिति को बदल सकता है (कई बार कॉल किया जाता है);
  • \n
  • Fn - यदि कॉलबैक स्थिति नहीं बदलता है (शुद्ध फ़ंक्शन)।
  • \n
\n

impl Fn के साथ उदाहरण:

\n
fn schedule<F>(callback: F)\nwhere\n    F: Fn(i32) -> i32,\n{\n    let result = 42;\n    println!("परिणाम: {}", callback(result));\n}\n\nfn main() {\n    let factor = 2;\n    let closure = |x| x * factor;\n    schedule(closure); // काम करता है!\n    \n    // एक सामान्य फ़ंक्शन भी पास किया जा सकता है\n    fn double(x: i32) -> i32 { x * 2 }\n    schedule(double);\n}
\n

लाभ: अत्यधिक लचीला, क्लोजर का समर्थन करता है, सुरक्षित, पढ़ने योग्य।
\nनुकसान: मोनोमोर्फिज़ेशन - प्रत्येक क्लोजर प्रकार के लिए कंपाइलर फ़ंक्शन का एक अलग संस्करण उत्पन्न करता है। यदि कई कॉलबैक हैं, तो कोड फूल सकता है।

\n\n

दृष्टिकोण 2: ट्रेट-ऑब्जेक्ट Box<dyn Fn> (डायनामिक डिस्पैच)

\n

यदि आप कॉलबैक को कलेक्शन (जैसे Vec) में स्टोर करना चाहते हैं या उन्हें थ्रेड्स के बीच पास करना चाहते हैं, तो जेनेरिक काम नहीं करेंगे - एक प्रकार की आवश्यकता है। ट्रेट-ऑब्जेक्ट मदद करते हैं:

\n
type Callback = Box<dyn Fn(i32) -> i32>;\n\nstruct Scheduler {\n    callbacks: Vec<Callback>,\n}\n\nimpl Scheduler {\n    fn new() -> Self {\n        Scheduler { callbacks: Vec::new() }\n    }\n    \n    fn register(&mut self, callback                

ब्लॉग

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