Should I pass function objects by value or by reference?
TL;DR: You should use F: Fn() -> ()
or impl Fn() -> ()
as an argument.
Fn
As @Bubletan mentioned in their answer, the key point is that Fn
is automatically implemented for &F
if F
implements Fn
:
impl<'_, A, F> Fn<A> for &'_ F
where
F: Fn<A> + ?Sized,
The consequence is that:
foo(f: impl Fn() -> ())
can be called both withfoo(callable)
orfoo(&callable)
.foo(f: &impl Fn() -> ())
forces the caller to usefoo(&callable)
and disallowfoo(callable)
.
In general, it is best to leave the choice to the caller when there is downside for the callee, and therefore the first form should be preferred.
FnMut
The same logic applies to FnMut
, which is also automatically implemented for &mut F
if F
implements FnMut
:
impl<'_, A, F> FnMut<A> for &'_ mut F
where
F: FnMut<A> + ?Sized,
Which should therefore also be passed by value in arguments, leaving the choice to the caller as to whether they prefer foo(callable)
or foo(&mut callable)
.
FnOnce
There is the argument of consistency with FnOnce
, which can only be passed by value, which again points into the direction of taking arguments of the Fn*
family by value.
The reason that Option::map
takes a closure by value, is that it has the following signature:
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U>
This therefore means that it needs to take it by value, because the definition of FnOnce
is the following:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
Also, this Fn
variant is the least restrictive, and therefore the most usable, because FnMut: FnOnce
and Fn: FnMut
, so FnOnce
is the least derived.
So, from this we can infer:
Option::map
is trying to make its arguments the least restrictiveFnOnce
is the least restrictiveFnOnce
needs to takeself
by value- Therefore
Option::map
takesf
by value because otherwise it'd be useless.
The documentation of the Fn trait states that if some type F implements Fn, then &F also implements Fn.
In the documentation of the Copy trait, it is mentioned that the trait is automatically implemented for function pointers and closures (depending on what they capture of course). That is, they are copied when passed as a parameter to a function.
Therefore you should go with the second option.
An example:
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
}