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.