संक्षिप्त उत्तर: अधिकतम लचीलेपन के लिए, आप कॉलबैक को कॉलबैक प्रकार के रूप FnMut
में कॉलबैक सेटर जेनेरिक के साथ एक बॉक्सिंग ऑब्जेक्ट के रूप में स्टोर कर सकते हैं । इसके लिए कोड उत्तर में अंतिम उदाहरण में दिखाया गया है। अधिक विस्तृत विवरण के लिए, पर पढ़ें।
"फ़ंक्शन पॉइंटर्स": कॉलबैक के रूप में fn
प्रश्न में C ++ कोड के निकटतम समकक्ष को कॉलबैक के रूप में घोषित किया जाएगा fn
। कीवर्ड fn
द्वारा परिभाषित फ़ंक्शंस fn
, C ++ के फ़ंक्शन पॉइंटर्स जैसे बहुत कुछ:
type Callback = fn();
struct Processor {
callback: Callback,
}
impl Processor {
fn set_callback(&mut self, c: Callback) {
self.callback = c;
}
fn process_events(&self) {
(self.callback)();
}
}
fn simple_callback() {
println!("hello world!");
}
fn main() {
let p = Processor {
callback: simple_callback,
};
p.process_events();
}
Option<Box<Any>>
फ़ंक्शन से जुड़े "उपयोगकर्ता डेटा" को रखने के लिए इस कोड को बढ़ाया जा सकता है । फिर भी, यह मुहावरेदार जंग नहीं होगी। किसी फ़ंक्शन के साथ डेटा को संबद्ध करने का रस्ट तरीका आधुनिक सी ++ की तरह ही एक अनाम क्लोजर में कैप्चर करना है । चूंकि क्लोजर नहीं हैं fn
, इसलिए set_callback
अन्य प्रकार के फ़ंक्शन ऑब्जेक्ट को स्वीकार करने की आवश्यकता होगी।
जेनेरिक फ़ंक्शन ऑब्जेक्ट के रूप में कॉलबैक
Rust और C ++ दोनों में एक ही कॉल सिग्नेचर के साथ क्लोजर अलग-अलग साइज में आते हैं ताकि वे अलग-अलग वैल्यू को एडजस्ट कर सकें। इसके अतिरिक्त, प्रत्येक क्लोजर परिभाषा क्लोजर के मूल्य के लिए एक अद्वितीय अनाम प्रकार उत्पन्न करता है। इन बाधाओं के कारण, संरचना अपने callback
क्षेत्र के प्रकार का नाम नहीं दे सकती है , न ही यह एक उपनाम का उपयोग कर सकती है।
एक ठोस प्रकार का जिक्र किए बिना संरचना क्षेत्र में एक बंद को एम्बेड करने का एक तरीका यह है कि संरचना को सामान्य बनाया जाए । संरचना स्वचालित रूप से अपने आकार और कंक्रीट फ़ंक्शन के लिए कॉलबैक के प्रकार या आपके द्वारा इसे पास करने के लिए बंद कर देगी:
struct Processor<CB>
where
CB: FnMut(),
{
callback: CB,
}
impl<CB> Processor<CB>
where
CB: FnMut(),
{
fn set_callback(&mut self, c: CB) {
self.callback = c;
}
fn process_events(&mut self) {
(self.callback)();
}
}
fn main() {
let s = "world!".to_string();
let callback = || println!("hello {}", s);
let mut p = Processor { callback: callback };
p.process_events();
}
पहले की तरह, कॉलबैक की नई परिभाषा के साथ परिभाषित शीर्ष-स्तरीय कार्यों को स्वीकार करने में सक्षम होगा fn
, लेकिन यह एक क्लोजर के || println!("hello world!")
साथ-साथ मानों को पकड़ने वाले क्लोजर को भी स्वीकार करेगा || println!("{}", somevar)
। इस वजह से प्रोसेसर userdata
को कॉलबैक के साथ जाने की जरूरत नहीं है ; के कॉलर द्वारा प्रदान किया गया क्लोजर set_callback
स्वचालित रूप से अपने वातावरण से इसकी जरूरत के डेटा को कैप्चर करेगा और इसे आह्वान करने पर उपलब्ध होगा।
लेकिन क्या है FnMut
, क्यों नहीं बस के साथ सौदा है Fn
? चूंकि क्लोजर पकड़े गए मानों को रोकते हैं, रुस्ट के सामान्य उत्परिवर्तन नियम बंद होने पर कॉल करते समय लागू होने चाहिए। वे जो मूल्य धारण करते हैं उसके साथ क्लोजर क्या होता है, इसके आधार पर, उन्हें तीन परिवारों में बांटा गया है, जिनमें से प्रत्येक को एक निशान के साथ चिह्नित किया गया है:
Fn
वे क्लोज़र हैं जो केवल डेटा पढ़ते हैं, और संभवतः कई बार, संभवतः कई थ्रेड्स से सुरक्षित रूप से कॉल किए जा सकते हैं। ऊपर के दोनों क्लोजर हैं Fn
।
FnMut
क्लोजर हैं जो डेटा को संशोधित करते हैं, उदाहरण के लिए कैप्चर किए गए mut
चर को लिखकर । उन्हें कई बार कहा जा सकता है, लेकिन समानांतर में नहीं। ( FnMut
कई थ्रेड से क्लोजर कॉल करने से डेटा रेस होगी, इसलिए यह केवल म्यूटेक्स के संरक्षण के साथ किया जा सकता है।) क्लोजर ऑब्जेक्ट को कॉल करने वाले के द्वारा म्यूट घोषित किया जाना चाहिए।
FnOnce
वे क्लोजर हैं जो कुछ ओड का उपभोग करते हैं जो वे कैप्चर करते हैं, उदाहरण के लिए एक कैप्चर किए गए वैल्यू को एक फंक्शन में ले जाकर जो उसका स्वामित्व लेता है। जैसा कि नाम से ही स्पष्ट है, ये केवल एक बार ही कहे जा सकते हैं, और कॉल करने वाले को इनका मालिक होना चाहिए।
कुछ हद तक प्रति-सहजता से, जब किसी वस्तु के प्रकार के लिए बाध्य विशेषता को निर्दिष्ट करता है जो एक क्लोजर को स्वीकार करता है, FnOnce
तो वास्तव में सबसे अधिक अनुमत है। यह घोषणा करते हुए कि जेनेरिक कॉलबैक प्रकार को FnOnce
विशेषता को संतुष्ट करना चाहिए, इसका मतलब है कि यह सचमुच किसी भी बंद को स्वीकार करेगा। लेकिन यह एक मूल्य के साथ आता है: इसका मतलब है कि धारक को केवल एक बार कॉल करने की अनुमति है। चूंकि process_events()
कई बार कॉलबैक को लागू करने का विकल्प चुन सकते हैं, और जैसा कि विधि को एक से अधिक बार कॉल किया जा सकता है, अगली सबसे अधिक अनुमत बाध्यता है FnMut
। ध्यान दें कि हमें process_events
म्यूटिंग के रूप में चिह्नित करना था self
।
गैर-जेनेरिक कॉलबैक: फ़ंक्शन विशेषता ऑब्जेक्ट
भले ही कॉलबैक का सामान्य कार्यान्वयन बेहद कुशल है, लेकिन इसमें गंभीर इंटरफ़ेस सीमाएँ हैं। यह प्रत्येक Processor
उदाहरण के लिए एक ठोस कॉलबैक प्रकार के साथ मानकीकृत होने की आवश्यकता है , जिसका अर्थ है कि एक एकल Processor
केवल एकल कॉलबैक प्रकार से निपट सकता है। यह देखते हुए कि प्रत्येक बंद का एक अलग प्रकार है, जेनेरिक उसके बाद Processor
नहीं संभाल सकता proc.set_callback(|| println!("hello"))
है proc.set_callback(|| println!("world"))
। दो कॉलबैक फ़ील्ड का समर्थन करने के लिए संरचना का विस्तार करने के लिए पूरे ढांचे को दो प्रकारों के लिए मानकीकृत करने की आवश्यकता होगी, जो कॉलबैक की संख्या बढ़ने पर जल्दी से अनजान बन जाएगा। अधिक प्रकार के मापदंडों को जोड़ने से काम नहीं चलेगा यदि कॉलबैक की संख्या गतिशील होने की आवश्यकता है, उदाहरण के लिए एक add_callback
फ़ंक्शन लागू करने के लिए जो अलग-अलग कमियों के वेक्टर को बनाए रखता है।
प्रकार पैरामीटर को हटाने के लिए, हम विशेषता वस्तुओं का लाभ उठा सकते हैं , जंग की विशेषता जो लक्षणों के आधार पर गतिशील इंटरफेस के स्वत: निर्माण की अनुमति देती है। इसे कभी-कभी टाइप इरेज़र के रूप में संदर्भित किया जाता है और यह C ++ [1] [2] में एक लोकप्रिय तकनीक है , जावा और एफपी भाषाओं के शब्द के कुछ अलग उपयोग के साथ भ्रमित होने की नहीं। सी ++ के साथ परिचित पाठकों कि औजार को बंद करने के बीच के अंतर को पहचान लेगा Fn
और एक Fn
सामान्य समारोह वस्तुओं और के बीच के अंतर के बराबर विशेषता वस्तु std::function
सी में मूल्यों ++।
किसी विशेषता वस्तु को &
ऑपरेटर के साथ एक वस्तु उधार लेकर और विशिष्ट विशेषता के संदर्भ में कास्टिंग या इसे बनाने के लिए बनाया जाता है । इस मामले में, चूंकि Processor
कॉलबैक ऑब्जेक्ट का स्वामी होना चाहिए, इसलिए हम उधार का उपयोग नहीं कर सकते हैं, लेकिन कॉलबैक को ढेर-आवंटित Box<dyn Trait>
(रस्ट समतुल्य std::unique_ptr
) में संग्रहित करना चाहिए , जो कार्यात्मक रूप से एक विशेषता ऑब्जेक्ट के बराबर है।
यदि Processor
स्टोर करता है Box<dyn FnMut()>
, तो उसे अब सामान्य होने की आवश्यकता नहीं है, लेकिन set_callback
विधि अब c
एक impl Trait
तर्क के माध्यम से एक सामान्य स्वीकार करती है । जैसे, यह किसी भी प्रकार के कॉल करने योग्य को स्वीकार कर सकता है, जिसमें राज्य के साथ क्लोजर शामिल हैं, और इसे स्टोर करने से पहले इसे ठीक से बॉक्स करें Processor
। जेनेरिक तर्क set_callback
यह सीमित नहीं करता है कि प्रोसेसर किस तरह के कॉलबैक को स्वीकार करता है, क्योंकि स्वीकृत कॉलबैक का प्रकार Processor
संरचना में संग्रहीत प्रकार से डिकॉउंड किया गया है ।
struct Processor {
callback: Box<dyn FnMut()>,
}
impl Processor {
fn set_callback(&mut self, c: impl FnMut() + 'static) {
self.callback = Box::new(c);
}
fn process_events(&mut self) {
(self.callback)();
}
}
fn simple_callback() {
println!("hello");
}
fn main() {
let mut p = Processor {
callback: Box::new(simple_callback),
};
p.process_events();
let s = "world!".to_string();
let callback2 = move || println!("hello {}", s);
p.set_callback(callback2);
p.process_events();
}
बॉक्सिंग क्लोजर के अंदर संदर्भों का जीवनकाल
'static
जीवन के प्रकार पर बाध्य c
तर्क द्वारा स्वीकार कर लिया set_callback
संकलक कि समझाने के लिए एक आसान तरीका है संदर्भ निहित में c
है, जो एक बंद है कि अपने पर्यावरण को संदर्भित करता है हो सकता है, केवल वैश्विक मूल्यों का उल्लेख है और इसलिए का उपयोग भर में मान्य रहेगा वापस कॉल करें। लेकिन स्टैटिक बाउंड भी बहुत भारी-भरकम होता है: जबकि यह बंद चीजों को स्वीकार करता है, जो खुद की वस्तुओं को ठीक बनाता है (जिसे हमने बंद करके सुनिश्चित किया है move
), यह उन क्लोजर को अस्वीकार करता है जो स्थानीय वातावरण का संदर्भ देते हैं, तब भी जब वे केवल मूल्यों का संदर्भ देते हैं प्रोसेसर को पछाड़ना और वास्तव में सुरक्षित होगा।
जब तक हमें केवल कॉलबैक की आवश्यकता होती है जब तक कि प्रोसेसर जीवित है, हमें उनके जीवनकाल को प्रोसेसर से जोड़ने का प्रयास करना चाहिए, जो कि कम से कम सख्त बाध्य है 'static
। लेकिन अगर हम अभी से 'static
आजीवन बंधे को हटा दें set_callback
, तो यह अब संकलित नहीं है। ऐसा इसलिए है क्योंकि set_callback
एक नया बॉक्स बनाता है और इसे इस callback
रूप में परिभाषित फ़ील्ड में असाइन करता है Box<dyn FnMut()>
। चूंकि परिभाषा बॉक्सिंग विशेषता ऑब्जेक्ट के लिए एक जीवनकाल को निर्दिष्ट नहीं करती है, 'static
निहित है, और असाइनमेंट प्रभावी रूप से आजीवन (कॉलबैक के एक अनाम मनमाने ढंग से जीवनकाल से 'static
) को चौड़ा करेगा , जो कि अस्वीकृत है। प्रोसेसर के लिए एक स्पष्ट जीवनकाल प्रदान करना और उस जीवनकाल को बॉक्स में संदर्भ और कॉलबैक में दिए गए संदर्भ दोनों के लिए टाई करना है set_callback
:
struct Processor<'a> {
callback: Box<dyn FnMut() + 'a>,
}
impl<'a> Processor<'a> {
fn set_callback(&mut self, c: impl FnMut() + 'a) {
self.callback = Box::new(c);
}
}
इन जन्मों को स्पष्ट किए जाने के साथ, इसका उपयोग करना आवश्यक नहीं है 'static
। क्लोजर अब स्थानीय s
ऑब्जेक्ट को संदर्भित कर सकता है , अर्थात अब नहीं होना चाहिए move
, बशर्ते कि यह सुनिश्चित करने s
की परिभाषा से पहले रखा जाता है p
कि स्ट्रिंग प्रोसेसर को आउटलाइज़ करता है।
CB
है'static
?