Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I pass function objects by value or by reference?

Tags:

rust

There seems to be two ways to pass functions as arguments which don't do dynamic dispatch:

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

Assuming the function is pure (i.e. can be called multiple times), my initial thoughts were that the best way is to pass by reference. This means a function object can be used more than once (as ownership isn't transferred), and in the more common case where it's just an anonymous closure, the reference indirection should be optimised away because the compiler knows exactly the function itself (so it can just be inlined).

However, I've noticed Option::map for example, passes it's closure by value, which made me think maybe I was doing something wrong.

Should I pass function objects by value or by reference? If there's no clear answer either way, what are the factors I should consider?

like image 743
Clinton Avatar asked Jun 03 '19 02:06

Clinton


People also ask

Should I pass by reference or value?

pass by reference. It doesn't matter if the parameters are primitive types, arrays, or objects, either a copy is made or an address is stored. As noted elsewhere, when objects are copied, the copy constructor is called to do the copying. Typically if you aren't going to change a variable, you use pass by value.

Why is it usually better to pass objects by reference than by value?

The reason is simple: if you passed by value, a copy of the object had to be made and, except for very small objects, this is always more expensive than passing a reference.

Is it faster to pass by reference or value?

As a rule of thumb, passing by reference or pointer is typically faster than passing by value, if the amount of data passed by value is larger than the size of a pointer.

Are functions pass by reference?

When a function is called, the arguments in a function can be passed by value or passed by reference. Callee is a function called by another and the caller is a function that calls another function (the callee). The values that are passed in the function call are called the actual parameters.

How does pass by reference work in C++?

In the case of pass by reference, the calling method passes variables to the called function and the called function accepts the argument as the address of those variables which are passed thru the calling function. I would suggest you read the code with the comments associated with them.

Is it possible to pass by reference or pass by value?

However, changing the value of the parameter to refer to a different object will not be visible when you're using pass by value, which is the default for all types. If you want to use pass-by-reference, you must use out or ref, whether the parameter type is a value type or a reference type.

What is the purpose of passing objects by const reference?

If you're calling a function that needs to take a large object as a parameter, pass it by const reference to avoid making an unnecessary copy of that object and taking a large efficiency hit.

What happens when you pass a parameter to a function by reference?

On the other hand, when you pass a value-type parameter to a function by reference, the changes you make to that parameter inside the function will change the original data.


1 Answers

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.

like image 154
Matthieu M. Avatar answered Oct 07 '22 00:10

Matthieu M.