似乎有两种方法可以将函数作为不执行动态调度的参数传递:
&impl Fn(TIn) -> TOut // By reference
impl Fn(TIn) -> TOut // By value
假设该函数是纯函数(即可以多次调用),我最初的想法是最好的方法是通过引用传递。这意味着函数对象可以多次使用(因为所有权不会转移),并且在更常见的情况下,它只是一个匿名闭包,引用间接寻址应该被优化,因为编译器确切地知道函数本身(所以它可以被内联)。
但是,我注意到Option::map
例如,按值传递其关闭,这让我觉得也许我做错了什么。
我应该按值还是按引用传递函数对象?如果无论哪种方式都没有明确的答案,我应该考虑哪些因素?
TL;大卫:你应该用F: Fn() -> ()
或impl Fn() -> ()
作为论据。
Fn
正如@Bubletan他们的回答中提到的,关键点是,如果F
实现Fn
,Fn
会自动实现&F
:
impl<'_, A, F> Fn<A> for &'_ F
where
F: Fn<A> + ?Sized,
其结果是:
foo(f: impl Fn() -> ())
既可以用foo(callable)
也可以用foo(&callable)
来调用。foo(f: &impl Fn() -> ())
强制调用方使用foo(&callable)
并禁止foo(callable)
。
一般来说,当被叫方有缺点时,最好将选择权留给调用方,因此应首选第一种形式。
FnMut
同样的逻辑也适用于FnMut
,如果F
实现FnMut
,它也会自动实现&mut F
:
impl<'_, A, F> FnMut<A> for &'_ mut F
where
F: FnMut<A> + ?Sized,
因此,这也应该在参数中按值传递,让调用者选择他们更喜欢foo(callable)
还是foo(&mut callable)
。
FnOnce
有与FnOnce
一致性的论证,只能通过值传递,这再次指向了按值获取Fn*
族参数的方向。
Option::map
按值收包的原因是它具有以下签名:
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U>
因此,这意味着它需要按值获取它,因为FnOnce
的定义如下:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
此外,这个Fn
变体的限制性最小,因此最有用,因为FnMut: FnOnce
和Fn: FnMut
,所以FnOnce
是派生最少的。
因此,由此我们可以推断:
Option::map
正试图使其论点限制最少FnOnce
限制最少FnOnce
需要按价值来self
- 因此,
Option::map
按价值获取f
,否则它将毫无用处。
Fn 特征的文档指出,如果某个类型 F 实现了 Fn,那么 &F 也实现了 Fn。
在 Copy 特征的文档中,提到该特征会自动实现函数指针和闭包(当然取决于它们捕获的内容)。也就是说,当它们作为参数传递给函数时,它们会被复制。
因此,您应该选择第二个选项。
举个例子:
fn foo(f: impl Fn(i32) -> i32) -> i32 { f(42) }
fn bar() {
let f = |x| -> 2 * x;
foo(f);
foo(f); // f is copied and can thus be used
}