callback - जंग में मुहावरेदार कॉलबैक



rust (1)

लघु उत्तर: अधिकतम लचीलेपन के लिए, आप कॉलबैक को कॉलबैक FnMut ऑब्जेक्ट के रूप में स्टोर कर सकते हैं, कॉलबैक प्रकार पर कॉलबैक सेटर जेनेरिक के साथ। इसके लिए कोड अंतिम उदाहरण में उत्तर में दिखाया गया है। अधिक विस्तृत विवरण के लिए, पर पढ़ें।

"फ़ंक्शन पॉइंटर्स": fn रूप में कॉलबैक

प्रश्न में C ++ कोड के सबसे नज़दीकी समकक्ष को fn प्रकार घोषित किया जाएगा। 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 mut p = Processor { callback: simple_callback };
    p.process_events();         // hello world!
}

फ़ंक्शन से जुड़े "उपयोगकर्ता डेटा" को रखने के लिए इस कोड को एक Option<Box<Any>> शामिल करने के लिए बढ़ाया जा सकता है। फिर भी, यह मुहावरेदार जंग नहीं होगी। किसी फ़ंक्शन के साथ डेटा को संबद्ध करने का रस्ट तरीका आधुनिक C ++ की तरह ही एक अनाम क्लोजर में कैप्चर करना है। चूंकि set_callback fn नहीं हैं, set_callback को अन्य प्रकार की फ़ंक्शन ऑब्जेक्ट्स को स्वीकार करने की आवश्यकता होगी।

जेनेरिक फ़ंक्शन ऑब्जेक्ट के रूप में कॉलबैक

रुस्ट और C ++ दोनों में एक ही कॉल सिग्नेचर के साथ क्लोजर ऑब्जेक्ट में स्टोर किए गए कैप्चर किए गए वैल्यू के विभिन्न आकारों को समायोजित करने के लिए अलग-अलग साइज़ में आते हैं। इसके अतिरिक्त, प्रत्येक क्लोजर साइट एक अलग अनाम प्रकार उत्पन्न करती है जो संकलन समय पर क्लोजर ऑब्जेक्ट का प्रकार है। इन बाधाओं के कारण, संरचना नाम या कॉल प्रकार से कॉलबैक प्रकार को संदर्भित नहीं कर सकती।

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

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!("hello world!") , साथ ही मानों को कैप्चर करने वाले क्लोजर जैसे कि || println!("{}", somevar) || println!("{}", somevar) । इस वजह से बंद करने के लिए एक अलग उपयोगकर्ता तर्क की आवश्यकता नहीं है; यह केवल अपने वातावरण से डेटा को कैप्चर कर सकता है और इसे कॉल करने पर उपलब्ध होगा।

लेकिन FnMut के साथ सौदा क्या है, सिर्फ Fn क्यों नहीं? चूंकि क्लोज़र पकड़े गए मानों को रोकते हैं, रुस्त उन पर समान नियम लागू करता है जो अन्य कंटेनर ऑब्जेक्ट्स पर लागू होता है। वे जो मूल्य धारण करते हैं उसके साथ क्लोजर क्या होता है, इसके आधार पर, उन्हें तीन परिवारों में बांटा गया है, जिनमें से प्रत्येक को एक निशान के साथ चिह्नित किया गया है:

  • Fn ऐसे क्लोजर हैं जो केवल डेटा को पढ़ते हैं, और कई थ्रेड से संभवतः कई बार सुरक्षित रूप से कॉल किए जा सकते हैं। ऊपर के दोनों बंद Fn
  • FnMut वे क्लोजर हैं जो डेटा को संशोधित करते हैं, जैसे कि FnMut किए गए FnMut में लिखकर। उन्हें कई बार कहा जा सकता है, लेकिन समानांतर में नहीं। ( FnMut को कई थ्रेड्स से बंद करने से डेटा रेस हो जाएगी, इसलिए यह केवल एक म्यूटेक्स के संरक्षण के साथ किया जा सकता है।) कॉल करने वाले द्वारा क्लोज़र ऑब्जेक्ट को म्यूट घोषित किया जाना चाहिए।
  • FnOnce वे क्लोजर हैं, जो उनके द्वारा कैप्चर किए गए डेटा का उपभोग करते हैं, जैसे कि इसे एक फंक्शन में ले जाकर जो उनका मालिक है। जैसा कि नाम से ही स्पष्ट है, ये केवल एक बार ही कहे जा सकते हैं, और कॉल करने वाले को इनका मालिक होना चाहिए।

कुछ हद तक प्रति-सहजता से, जब एक वस्तु के प्रकार के लिए बाध्य विशेषता को निर्दिष्ट करता है, जो एक क्लोजर को स्वीकार करता है, तो FnOnce वास्तव में सबसे अधिक अनुमत है। यह घोषणा करते हुए कि जेनेरिक कॉलबैक प्रकार को FnOnce विशेषता से संतुष्ट होना चाहिए, इसका मतलब है कि यह सचमुच किसी भी बंद को स्वीकार करेगा। लेकिन यह एक मूल्य के साथ आता है: इसका मतलब है कि धारक को केवल एक बार कॉल करने की अनुमति है। चूंकि process_events() कॉलबैक को कई बार लागू करने का विकल्प चुन सकता है, और विधि के रूप में स्वयं को एक से अधिक बार कॉल किया जा सकता है, अगली सबसे अधिक अनुमत बाध्य FnMut । ध्यान दें कि हमें self को process_events रूप में process_events को चिह्नित करना था।

गैर-जेनेरिक कॉलबैक: फ़ंक्शन विशेषता ऑब्जेक्ट

भले ही कॉलबैक का सामान्य कार्यान्वयन बेहद कुशल है, लेकिन इसमें गंभीर इंटरफ़ेस सीमाएँ हैं। यह प्रत्येक Processor इंस्टेंस को एक ठोस कॉलबैक प्रकार के साथ मानकीकृत करने की आवश्यकता है, जिसका अर्थ है कि एक एकल Processor केवल एक सिंगल कॉलबैक प्रकार से निपट सकता है। यह देखते हुए कि प्रत्येक क्लोजर का एक अलग प्रकार है, जेनेरिक Processor proc.set_callback(|| println!("hello")) proc.set_callback(|| println!("world")) proc.set_callback(|| println!("hello")) बाद proc.set_callback(|| println!("world")) को नहीं संभाल सकता है। दो कॉलबैक फ़ील्ड का समर्थन करने के लिए संरचना का विस्तार करने के लिए पूरे ढांचे को दो प्रकारों के लिए मानकीकृत करने की आवश्यकता होगी, जो कॉलबैक की संख्या बढ़ने पर जल्दी से अनजान बन जाएगा। अधिक प्रकार के मापदंडों को जोड़ने से काम नहीं चलेगा, यदि कॉलबैक की संख्या गतिशील होने की जरूरत है, उदाहरण के लिए एक add_callback फ़ंक्शन को लागू करने के लिए जो विभिन्न कॉलबैक के वेक्टर को बनाए रखता है।

प्रकार पैरामीटर को हटाने के लिए, हम विशेषता वस्तुओं का लाभ उठा सकते हैं, जंग की विशेषता जो लक्षणों के आधार पर गतिशील इंटरफेस के स्वत: निर्माण की अनुमति देती है। इसे कभी-कभी टाइप इरेज़र के रूप में संदर्भित किया जाता है और यह C ++ [1] [2] में एक लोकप्रिय तकनीक है, जावा और एफपी भाषाओं के शब्द के कुछ अलग उपयोग के साथ भ्रमित होने की नहीं। C ++ से परिचित पाठक एक क्लोजर के बीच के अंतर को पहचानेंगे जो Fn और एक Fn विशेषता ऑब्जेक्ट को सामान्य फ़ंक्शन ऑब्जेक्ट्स और std::function मानों के बीच C ++ के अंतर के बराबर लागू std::function है।

किसी ऑब्जेक्ट को & ऑपरेटर के साथ किसी ऑब्जेक्ट को उधार लेकर और विशिष्ट विशेषता के संदर्भ में कास्टिंग या कॉर्किंग करके बनाया जाता है। इस मामले में, चूंकि Processor को कॉलबैक ऑब्जेक्ट का मालिक होना चाहिए, हम उधार का उपयोग नहीं कर सकते हैं, लेकिन कॉलबैक को ढेर-आवंटित Box<Trait> ( std::unique_ptr का रस्ट समतुल्य std::unique_ptr ) में std::unique_ptr , जो कि एक विशेषता के बराबर कार्यात्मक है। वस्तु।

यदि Processor Box<FnMut()> को set_callback है, तो उसे अब जेनेरिक होने की आवश्यकता नहीं है, लेकिन set_callback विधि अब जेनेरिक है, इसलिए यह Processor में बॉक्स को संग्रहीत करने से पहले आपको जो भी set_callback करने योग्य है, उसे ठीक से बॉक्स कर सकता है। कॉलबैक किसी भी प्रकार का हो सकता है जब तक कि वह कैप्चर किए गए मानों का उपभोग नहीं करता है। set_callback सामान्य होने के कारण ऊपर चर्चा की गई सीमाएँ नहीं होती हैं, क्योंकि यह संरचना में संग्रहीत डेटा के इंटरफ़ेस को प्रभावित नहीं करती है।

struct Processor {
    callback: Box<FnMut()>,
}

impl Processor {
    fn set_callback<CB: 'static + FnMut()>(&mut self, c: CB) {
        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();
}

C / C ++ में, मैं सामान्य रूप से एक सादे फ़ंक्शन पॉइंटर के साथ कॉलबैक करता हूं, शायद एक void* userdata पैरामीटर भी पास कर रहा है। कुछ इस तरह:

typedef void (*Callback)();

class Processor
{
public:
    void setCallback(Callback c)
    {
        mCallback = c;
    }

    void processEvents()
    {
        for (...)
        {
            ...
            mCallback();
        }
    }
private:
    Callback mCallback;
};

रूस्तम में ऐसा करने का मुहावरेदार तरीका क्या है? विशेष रूप से, मेरे setCallback() फ़ंक्शन को किस प्रकार लेना चाहिए, और किस प्रकार का mCallback होना चाहिए? यह एक Fn लेना चाहिए? शायद FnMut ? क्या मैं इसे सहेज कर Boxed हूं? एक उदाहरण अद्भुत होगा।