4
fn func(_: i64) -> bool {
    true
}

fn func_of_func(callback: &fn(i64) -> bool, arg: i64) -> bool {
    (*callback)(arg)
}

fn main() {
    let is_positive = &func;
    println!("{}", func_of_func(is_positive, 8));
    println!("{}", func_of_func(is_positive, 8));
}

This doesn't compile:

error[E0308]: mismatched types
 --> src/main.rs:9:33
  |
9 |     println!("{}", func_of_func(is_positive, 8));
  |                                 ^^^^^^^^^^^ expected fn pointer, found fn item
  |
  = note: expected reference `&fn(i64) -> bool`
             found reference `&fn(i64) -> bool {func}`

Why does this error occur while I have passed a pointer, not fn? I want to know the practical difference between using fn and pointer to fn.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
asmmo
  • 6,922
  • 1
  • 11
  • 25

2 Answers2

4

fn(i64) -> bool is already a function pointer, so &fn(i64) -> bool is a reference to a function pointer. Since function pointers are Copy, you should never have any reason to write this.

If you're writing a function that takes something function-like as an argument, you should usually use generics (or impl Fn, as in Mike Graham's answer, which means the same thing):

fn func_of_func<F: FnOnce(i64) -> bool>(callback: F, arg: i64) -> bool {
    callback(arg)
}

This means that when you call func_of_func with a function item such as func, callback will be compiled to a direct function call instead of a function pointer, which is easier for the compiler to optimize.

If the function cannot be made generic (perhaps because it's a member of an object safe trait), you should usually use trait objects instead, which allows the caller to pass a closure:

fn func_of_func(callback: &dyn Fn(i64) -> bool, arg: i64) -> bool { ... }
fn func_of_func(callback: &mut dyn FnMut(i64) -> bool, arg: i64) -> bool { ... }
// using `FnOnce` requires boxing
fn func_of_func(callback: Box<dyn FnOnce(i64) -> bool>, arg: i64) -> bool { ... }

Function pointers should only be used when the function definitely cannot capture anything. They are mostly useful for FFI with C, and as type parameters to PhantomData in generic structs.

References

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
trent
  • 25,033
  • 7
  • 51
  • 90
  • As for " a direct function call instead of a function pointer, which is easier for the compiler to optimize", what kind of optimization would a compiler do here? – Ze Gao Mar 03 '22 at 02:12
  • @ZeGao [Inlining](https://en.wikipedia.org/wiki/Inline_expansion) the function call in `callback(arg)` is trivial when `callback` is a statically known function, but nigh-impossible when `callback` is a function pointer. – trent Mar 07 '22 at 21:54
2

You should be able to fix this with

fn func_of_func(callback: &fn(i64) -> bool, arg: i64) -> bool {
    (*callback)(arg)
}

fn main() {
    let is_positive = func;
    println!("{}", func_of_func(&is_positive, 8));
    println!("{}", func_of_func(&is_positive, 8));
}

Or more straightforwardly by not adding a level of indirection

fn func_of_func(callback: fn(i64) -> bool, arg: i64) -> bool {
    callback(arg)
}

fn main() {
    let is_positive = func;
    println!("{}", func_of_func(is_positive, 8));
    println!("{}", func_of_func(is_positive, 8));
}

It's more common to use Fn traits, which have the benefit of allowing closures as well as functions

fn func(x: i64) -> bool {
    true
}
    
fn func_of_func(callback: impl FnOnce(i64) -> bool, arg: i64) -> bool {
    callback(arg)
}

fn main() {
    let is_positive = func;
    println!("{}", func_of_func(is_positive, 8));
    println!("{}", func_of_func(is_positive, 8));
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
Mike Graham
  • 73,987
  • 14
  • 101
  • 130