There is one little problem though: It can sometimes change or hide the asymptotic complexity of a function.
For example, this function calls f(0),f(1), f(2), … f(n):
fn foo<F: Fn(u32)>(f: &F, n: u32) {
if n != 0 { foo(f, n-1); }
f(n);
}
This function runs in O(n).
However, if your proposal is allowed, then it is possible to write this:
fn foo<F: Fn(u32)>(f: F, n: u32) {
if n != 0 { foo(&f, n); }
f(n);
}
Which, unfortunately, runs in O(n^2). It might not be so obvious, so here is the explanation:
When calling f(n), f doesn’t have to be dereferenced.
When calling f(n-1), f has to be dereferenced once.
When calling f(n-2), f has to be dereferenced 2 times.
…
When calling f(0), f has to be be dereferenced n times
Thus, there must be a total of n*(n+1)/2 dereferences.
For non-recursive functions (i.e. most of the higher-order functions), it is possible to write |x| foo(x) to convert from &Fn(T) to Fn(T). Most code use closure literals directly anyway, so this workaround is rare.
For recursive functions, I think that explicit &F's in the function signature is OK.
Note that I realized this when I was writing actual code and I wrote something similar to this:
fn foo<F: Fn(u32)>(f: F, n: u32) {
if n != 0 { foo(|x| f(x), n); }
f(n);
}
fn main() {
foo(|_| {}, 10);
}
playpen
The result was: error: reached the recursion limit during monomorphization, which made me notice that O(n^2) blow up.