似乎有两种方法可以将函数作为参数传递,而不进行动态分配:
&impl Fn(TIn) -> TOut // By reference
impl Fn(TIn) -> TOut // By value
假设函数是纯函数(即可以多次调用),我最初的想法是最好的方法是通过引用传递。这意味着一个函数对象可以多次使用(因为所有权不会转移),在更常见的情况下,它只是一个匿名闭包,应该优化引用的间接引用,因为编译器完全知道函数本身(因此可以内联)。
但是,例如,我注意到Option::map
通过值传递了它的闭包,这让我觉得也许我做错了什么。
我应该按值还是按引用传递函数对象?如果两种方法都没有明确的答案,我应该考虑哪些因素?
答案 0 :(得分:7)
TL; DR:您应该使用F: Fn() -> ()
或impl Fn() -> ()
作为参数。
Fn
@Bubletan提到in their answer,关键是如果Fn
实现&F
,则F
是Fn
的{{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;
}
此外,由于Fn
和FnMut: FnOnce
,因此Fn: FnMut
的派生最少。
因此,我们可以据此推断:
FnOnce
试图使其参数的限制最小化Option::map
的限制最少FnOnce
需要按值获取FnOnce
self
取值Option::map
,否则it'd be useless。 答案 2 :(得分:2)