Rust में क्लोज़र्स: जादू या सख्त गणित?
\n\nयदि आपने कभी Rust में |x| x + 1 जैसा कुछ लिखा है, तो आपने क्लोज़र्स का उपयोग किया है। लेकिन जैसे ही आप इस क्लोज़र को किसी फंक्शन में पास करने या किसी स्ट्रक्चर में स्टोर करने का प्रयास करते हैं, कंपाइलर अचानक मांग करने लगता है: “कौन सा ट्रेट? Fn, FnMut या FnOnce?”।
और यहाँ कई लोग अटक जाते हैं। खासकर यदि आप JavaScript, Python या Ruby से आए हैं, जहाँ क्लोज़र्स सिर्फ “फंक्शन होते हैं जो वेरिएबल्स को कैप्चर करते हैं”। Rust में यह अधिक जटिल (और दिलचस्प) है।
\n\nआज हम समझेंगे कि जब Rust यह तय करता है कि आपके क्लोज़र को कौन सा ट्रेट देना है, तो हुड के नीचे वास्तव में क्या होता है। यह जादू नहीं है — यह एक सख्त टाइप सिस्टम है जो आपको डेटा रेस और हैंगिंग पॉइंटर्स से बचाता है। चलिए शुरू करते हैं!
\n\nमुख्य बात संक्षेप में: क्लोज़र्स के तीन ट्रेट्स
\n\nRust में तीन ट्रेट्स हैं जो क्लोज़र्स को इम्प्लीमेंट करते हैं:
\n\n- \n
FnOnce— इसे एक बार कॉल किया जा सकता है। यह कैप्चर किए गए वेरिएबल्स का स्वामित्व ले लेता है। \n FnMut— इसे कई बार कॉल किया जा सकता है, लेकिन यह कैप्चर किए गए वेरिएबल्स को बदल सकता है (&mutलेता है)। \n Fn— इसे कई बार कॉल किया जा सकता है और यह कैप्चर किए गए वेरिएबल्स को नहीं बदलता (&लेता है)। \n
प्रत्येक क्लोज़र स्वचालित रूप से सभी उपयुक्त ट्रेट्स को इम्प्लीमेंट करता है। अर्थात यदि कोई क्लोज़र Fn को इम्प्लीमेंट करता है, तो वह स्वचालित रूप से FnMut और FnOnce को भी इम्प्लीमेंट करता है। लेकिन उल्टा काम नहीं करता।
क्यों? क्योंकि Fn सबसे सख्त है, और FnOnce सबसे कमजोर। इनहेरिटेंस का पदानुक्रम इस प्रकार है:
FnOnce ← FnMut ← Fn\n\nअर्थात FnMut: FnOnce, और Fn: FnMut। इस सीढ़ी को याद रखें — यह महत्वपूर्ण है।
Rust कैसे तय करता है कि क्लोज़र को कौन सा ट्रेट देना है?
\n\nकंपाइलर देखता है कि क्लोज़र कैप्चर किए गए वेरिएबल्स का उपयोग कैसे करता है। कैप्चर करने के तीन तरीके हैं:
\n\n- \n
- रेफरेंस द्वारा (
&T) — क्लोज़र केवल वेरिएबल को पढ़ता है। \n - म्यूटेबल रेफरेंस द्वारा (
&mut T) — क्लोज़र वेरिएबल को बदलता है। \n - वैल्यू द्वारा (
T) — क्लोज़र वेरिएबल का स्वामित्व ले लेता है। \n
यहाँ बताया गया है कि यह ट्रेट्स को कैसे प्रभावित करता है:
\n\n| कैप्चर करने का तरीका | इम्प्लीमेंट किए गए ट्रेट्स | उदाहरण |
|---|---|---|
केवल &T | Fn + FnMut + FnOnce | || println!("{}", x) |
&mut T (लेकिन T नहीं) | FnMut + FnOnce | || x += 1 |
T (स्वामित्व) | केवल FnOnce | || drop(x) या move || x |
आइए प्रत्येक मामले को लाइव कोड के साथ समझते हैं।
\n\nउदाहरण 1: केवल पढ़ना → Fn
\n\nlet x = 42;\nlet read_closure = || println!("x = {}", x);\n\n// इसे जितनी बार चाहें कॉल कर सकते हैं\nread_closure();\nread_closure();\n\n// और इसे Fn की आवश्यकता वाले फंक्शन में भी पास कर सकते हैं\nfn call_fn<F: Fn()>(f: F) {\n f();\n}\ncall_fn(read_closure);\n\nयहाँ क्लोज़र बस x को पढ़ रहा है। इसे बदलने या लेने की आवश्यकता नहीं है। Rust समझता है: “ठीक है, यह सुरक्षित है — Fn देते हैं”।
उदाहरण 2: बदलना → FnMut
\n\nlet mut x = 42;\nlet mut mut_closure = || {\n x += 1;\n println!("x = {}", x);\n};\n\nmut_closure(); // x = 43\nmut_closure(); // x = 44\n\n// FnMut की आवश्यकता वाले फंक्शन में पास करते हैं\nfn call_fn_mut<F: FnMut()>(mut f: F) {\n f();\n f();\n}\ncall_fn_mut(mut_closure);\n\nध्यान दें: हमने mut_closure को let mut के रूप में घोषित किया। क्यों? क्योंकि FnMut को कॉल करने के लिए म्यूटेबल