【问题标题】:How do you pass a Rust function as a parameter?如何将 Rust 函数作为参数传递?
【发布时间】:2016-07-23 06:53:19
【问题描述】:

我可以将函数作为参数传递吗?如果没有,有什么好的选择?

我尝试了一些不同的语法,但我没有找到正确的语法。我知道我可以做到:

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
}

【问题讨论】:

    标签: rust


    【解决方案1】:

    当然可以:

    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);
    }
    

    由于这是 Rust,您必须考虑到 ownership and lifetime of the closure

    TL;DR;基本上有 3 种类型的闭包(可调用对象):

    1. Fn:不能修改捕获的对象。
    2. FnMut:它可以修改它捕获的对象。
    3. FnOnce:最受限制的。只能调用一次,因为调用时它会消耗自身及其捕获。

    详情请见When does a closure implement Fn, FnMut and FnOnce?

    如果您使用的是像闭包这样简单的指向函数的指针,那么捕获集是空的,并且您具有 Fn 风格。

    如果你想做更多花哨的东西,那么你将不得不使用 lambda 函数。

    在 Rust 中有正确的指向函数的指针,它们的工作方式与 C 中的一样。它们的类型例如是 fn(i32) -> i32Fn(i32) -> i32FnMut(i32) -> i32FnOnce(i32) -> i32 实际上是特征。指向函数的指针总是实现所有这三个,但 Rust 也有闭包,可能会或可能不会转换为指向函数的指针(取决于捕获集是否为空),但它们确实实现了其中一些特征。

    例如,上面的例子可以展开:

    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
    }
    

    【讨论】:

    • 使用 与不使用(.., f: &Fn...)这两个作品是有区别的,有些细节我需要知道吗?
    • @AngelAngel:嗯,Fn* 是特征,所以通常的 <T: Trait>(t: &T) 都适用。非泛型解决方案的主要限制是它必须与引用一起使用。所以如果你想要FnOnce,它应该作为副本传递,你必须使用通用样式。
    • 请注意,使用泛型而不是 trait 对象更为惯用(即 <F: Fn..> 而不是 (f: &Fn...)。这是有原因的 - 泛型将导致静态调度,而 trait 对象需要动态调度。
    • 有趣的是,从 interface(调用者)的角度来看,FnOnce 实际上是最通用的特征——它接受所有闭包,无论它们是读取、修改还是取得所有权的捕获状态。 FnMut 更具限制性,它不接受获取捕获对象所有权的闭包(但它仍然允许修改状态)。 Fn 是最严格的,因为它不接受修改其捕获状态的闭包。因此,要求&FnfunTest 调用者的限制最大,而对如何在其中调用f 的限制最小。
    【解决方案2】:

    FnFnMutFnOnce,在另一个答案中概述,是 closure 类型。超出其范围的函数类型。

    除了传递闭包之外,Rust 还支持传递简单(非闭包)函数,如下所示:

    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 这里是function pointer type

    如果您不需要一个成熟的闭包,那么使用函数类型通常会更简单,因为它不必处理那些闭包生命周期的细节。

    【讨论】:

    • @IvanTemchenko 也许吧?这里有一些代码供你使用:play.rust-lang.org/…
    • 这不是我的意思 =) 找到了返回 dyn 闭包的解决方法,该闭包捕获了自己的状态,所以我不需要传递实例引用...
    猜你喜欢
    • 2015-01-20
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    • 1970-01-01
    • 1970-01-01
    • 2021-01-14
    • 2021-10-30
    • 1970-01-01
    相关资源
    最近更新 更多