आप एक रस्ट फ़ंक्शन को एक पैरामीटर के रूप में कैसे पास करते हैं?


89

क्या मैं एक फ़ंक्शन को एक पैरामीटर के रूप में पारित कर सकता हूं? यदि नहीं, तो एक अच्छा विकल्प क्या है?

मैंने कुछ अलग सिंटैक्स आज़माए लेकिन मुझे सही नहीं मिला। मैं जानता हूँ मैं यह कर सकता हूँ:

fn example() {
    let fun: fn(value: i32) -> i32;
    fun = fun_test;
    fun(5i32);
}

fn fun_test(value: i32) -> i32 {
    println!("{}", value);
    value
}

लेकिन वह फ़ंक्शन को दूसरे फ़ंक्शन के पैरामीटर के रूप में पारित नहीं कर रहा है:

fn fun_test(value: i32, (some_function_prototype)) -> i32 {
    println!("{}", value);
    value
}

जवाबों:


124

सुनिश्चित करें कि आप कर सकते हैं:

fn fun_test(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    fun_test(5, &times2);
}

जैसा कि यह जंग है, आपको बंद के स्वामित्व और जीवनकाल को ध्यान में रखना होगा ।

टीएल; डीआर; मूल रूप से 3 प्रकार के क्लोजर (कॉल करने योग्य ऑब्जेक्ट) हैं:

  1. Fn: यह कैप्चर की गई वस्तुओं को संशोधित नहीं कर सकता है।
  2. FnMut: यह कैप्चर की गई वस्तुओं को संशोधित कर सकता है।
  3. FnOnce: सबसे प्रतिबंधित। केवल एक बार ही बुलाया जा सकता है क्योंकि जब इसे बुलाया जाता है तो यह अपने आप को और इसके कैप्चर को खा जाता है।

देखें कि Fn, FnMut और FnOnce को कब बंद किया जाता है? अधिक जानकारी के लिए

यदि आप क्लोजर जैसे एक साधारण पॉइंटर-टू-फंक्शन का उपयोग कर रहे हैं, तो कैप्चर सेट खाली है और आपके पास Fnस्वाद है।

यदि आप अधिक फैंसी सामान करना चाहते हैं, तो आपको लैम्ब्डा कार्यों का उपयोग करना होगा।

जंग में कार्यों के लिए उचित संकेत हैं, जो सी में उन लोगों की तरह काम करते हैं। उनका प्रकार उदाहरण के लिए है fn(i32) -> i32Fn(i32) -> i32, FnMut(i32) -> i32और FnOnce(i32) -> i32वास्तव में लक्षण हैं। किसी फ़ंक्शन का पॉइंटर हमेशा इन तीनों को लागू करता है, लेकिन रस्ट में भी क्लोजर होते हैं, जो कि पॉइंटर्स में परिवर्तित हो सकते हैं या नहीं हो सकते हैं (इस पर निर्भर करता है कि कैप्चर सेट खाली है) फ़ंक्शंस में हैं, लेकिन वे इन लक्षणों में से कुछ को लागू करते हैं।

तो उदाहरण के लिए, ऊपर से उदाहरण का विस्तार किया जा सकता है:

fn fun_test_impl(value: i32, f: impl Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_dyn(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_ptr(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    let y = 2;
    //static dispatch
    fun_test_impl(5, times2);
    fun_test_impl(5, |x| 2*x);
    fun_test_impl(5, |x| y*x);
    //dynamic dispatch
    fun_test_dyn(5, &times2);
    fun_test_dyn(5, &|x| 2*x);
    fun_test_dyn(5, &|x| y*x);
    //C-like pointer to function
    fun_test_ptr(5, times2);
    fun_test_ptr(5, |x| 2*x); //ok: empty capture set
    fun_test_ptr(5, |x| y*x); //error: expected fn pointer, found closure
}

1
<F: Fn ...> या नहीं (.., f: & Fn ...) दो कार्यों का उपयोग करने में कुछ अंतर है, जो मुझे जानना आवश्यक है?
एंजेल

@AngelAngel: ठीक है, Fn*लक्षण हैं, इसलिए सामान्य <T: Trait>बनाम (t: &T)लागू होता है। गैर-जेनेरिक समाधान की मुख्य सीमा यह है कि इसका उपयोग संदर्भों के साथ किया जाना चाहिए। इसलिए यदि आप चाहते हैं FnOnce, जिसे एक प्रति के रूप में पारित किया जाना चाहिए, तो आपको सामान्य शैली का उपयोग करना चाहिए।
रॉडरिगो

5
ध्यान दें कि अनुगामी वस्तुओं के बजाय जेनेरिक का उपयोग करना अधिक मुहावरेदार है (अर्थात <F: Fn..>इसके बजाय (f: &Fn...)। और यह एक कारण के लिए है - जेनेरिक के परिणामस्वरूप स्थैतिक प्रेषण होगा, जबकि पारगमन की वस्तुओं को गतिशील प्रेषण की आवश्यकता होती है।
व्लादिमीर मतवेव

3
दिलचस्प है, एक इंटरफ़ेस (कॉलर) के दृष्टिकोण से, FnOnceवास्तव में सबसे सामान्य लक्षण है- यह सभी बंदियों को स्वीकार करता है चाहे वे पढ़े, संशोधित करें या कब्जा किए गए राज्य का स्वामित्व लें। FnMutयह अधिक प्रतिबंधक है, यह उन क्लोज़र को स्वीकार नहीं करता है जो एक कैप्चर की गई वस्तु का स्वामित्व लेते हैं (लेकिन यह अभी भी राज्य के संशोधनों की अनुमति देता है)। Fnसबसे अधिक प्रतिबंधात्मक है क्योंकि यह उन क्लोज़र को स्वीकार नहीं करता है जो उनकी कैप्चर की गई स्थिति को संशोधित करते हैं। इसलिए फोन करने वाले &Fnपर सबसे बड़ी पाबंदी लगाने की आवश्यकता होती है funTest, जबकि fइसके अंदर कैसे प्रवेश किया जा सकता है , इस पर कम से कम प्रतिबंध प्रदान करता है।
user4815162342

30

Fn, FnMutऔर FnOnce, अन्य उत्तर में उल्लिखित, क्लोजर प्रकार हैं। कार्यों के प्रकार जो उनके दायरे के करीब हैं।

पासिंग क्लोज़र के अलावा रस्ट पासिंग सिंपल (नॉन-क्लोजर) फंक्शन को भी सपोर्ट करता है, जैसे:

fn times2(value: i32) -> i32 {
    2 * value
}

fn fun_test(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f (value));
    value
}

fn main() {
    fun_test (2, times2);
}

fn(i32) -> i32यहाँ एक फ़ंक्शन पॉइंटर प्रकार है

यदि आपको फ़ंक्शन प्रकारों के साथ काम करने की तुलना में पूर्ण-पूर्ण बंद होने की आवश्यकता नहीं है, तो यह अक्सर सरल होता है क्योंकि इसमें उन करीबी जीवनकाल के लोगों के साथ व्यवहार नहीं करना पड़ता है।


क्या यह संरचना के तरीकों के साथ काम करेगा?
इवान टेम्सचोको

शायद @IvanTemchenko? यहाँ आपके साथ खेलने के लिए कुछ कोड है: play.rust-lang.org/…
ArtemGr

यह वास्तव में मैं क्या मतलब नहीं है =) dyn को बंद करने के लिए वर्कअराउंड मिला, जो स्वयं की स्थिति को कैप्चर करता है, इसलिए मुझे उदाहरण के लिए पास होने की आवश्यकता नहीं है ...
इवान टेम्सचोको
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.