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 with foo(callable) or foo(&callable).
  • foo(f: &impl Fn() -> ()) forces the caller to use foo(&callable) and disallow foo(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 restrictive
  • FnOnce is the least restrictive
  • FnOnce needs to take self by value
  • Therefore Option::map takes f 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
}

Tags:

Rust