我应该按值还是按引用传递函数对象?



似乎有两种方法可以将函数作为不执行动态调度的参数传递:

  1. &impl Fn(TIn) -> TOut // By reference
  2. impl Fn(TIn) -> TOut // By value

假设该函数是纯函数(即可以多次调用),我最初的想法是最好的方法是通过引用传递。这意味着函数对象可以多次使用(因为所有权不会转移),并且在更常见的情况下,它只是一个匿名闭包,引用间接寻址应该被优化,因为编译器确切地知道函数本身(所以它可以被内联)。

但是,我注意到Option::map例如,按值传递其关闭,这让我觉得也许我做错了什么。

我应该按值还是按引用传递函数对象?如果无论哪种方式都没有明确的答案,我应该考虑哪些因素?

TL;大卫:你应该用F: Fn() -> ()impl Fn() -> ()作为论据。

Fn

正如@Bubletan他们的回答中提到的,关键点是,如果F实现FnFn会自动实现&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: FnOnceFn: 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
}

最新更新