如何传递Rust函数作为参数?


81

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

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

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
}

Answers:


111

你当然可以:

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,因此您必须考虑closure所有权和生存期

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

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

请参阅闭包何时实现Fn,FnMut和FnOnce?更多细节

如果您使用的是简单的指向函数的指针(如闭包),则捕获集为空并且具有Fn味道。

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

在Rust中,有适当的指向函数的指针,这些指针的工作方式与C语言中的函数相同。例如,它们的类型fn(i32) -> i32。的Fn(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
}

1
是否使用<F:Fn ...>和(..,f:&Fn ...)这两个作品有区别,我需要知道一些细节吗?
Angel Angel

@AngelAngel:好吧,Fn*是特质,所以通常的<T: Trait>vs(t: &T)适用。非通用解决方案的主要局限性在于它必须与引用一起使用。因此,如果您希望FnOnce将其作为副本传递,则必须使用通用样式。
rodrigo

5
请注意,这是更地道使用仿制药,而不是特质的对象(即<F: Fn..>,而不是(f: &Fn...)这是有原因的-仿制药将导致静态调度,而特质对象需要动态分配。
弗拉基米尔Matveev

3
有趣的是,从接口(调用者)的角度来看,FnOnce它实际上是最通用的特征-它接受所有闭包,而不管它们是读取,修改还是获取已捕获状态的所有权。FnMut更为严格,它不接受采用已捕获对象所有权的闭包(但仍允许状态修改)。Fn是最严格的限制,因为它不接受修改其捕获状态的闭包。因此,要求&FnfunTest调用方上设置了最大的限制,而f在调用方内部如何调用的限制最小。
user4815162342

29

FnFnMutFnOnce在对方的回答概括,是封闭的类型。在其范围内关闭的函数的类型。

除传递闭包外,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这是一个函数指针类型

如果您不需要完整的闭包,那么使用函数类型通常会更简单,因为它不必处理那些闭包生存期的问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.