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

时间:2019-06-03 02:46:48

标签: rust

似乎有两种方法可以将函数作为参数传递,而不进行动态分配:

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

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

但是,例如,我注意到Option::map通过值传递了它的闭包,这让我觉得也许我做错了什么。

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

3 个答案:

答案 0 :(得分:7)

TL; DR:您应该使用F: Fn() -> ()impl Fn() -> ()作为参数。

Fn

@Bubletan提到in their answer,关键是如果Fn实现&F,则FFn的{​​{3}}:

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适用相同的逻辑,如果&mut F实现F,则FnMut的{​​{3}}也适用:

impl<'_, A, F> FnMut<A> for &'_ mut F
where
    F: FnMut<A> + ?Sized, 

因此,还应在参数中按值传递值,从而使调用者可以选择使用foo(callable)还是foo(&mut callable)

FnOnce

存在与FnOnce保持一致的论点,该论点只能通过值来传递,这再次指向按值接受Fn*族论点的方向。

答案 1 :(得分:2)

Option::map按值结束的原因是它具有following signature

pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U>

因此,这意味着需要按值对其进行处理,因为FnOnce的定义是following

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

此外,由于FnFnMut: FnOnce,因此Fn: FnMut的派生最少。
因此,我们可以据此推断:

  • FnOnce试图使其参数的限制最小化
  • Option::map的限制最少
  • FnOnce需要按值获取FnOnce
  • 因此,self取值Option::map,否则it'd be useless

答案 2 :(得分:2)

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
}