"Expected fn item, found a different fn item" when working with function pointers

Each named function has a distinct type since Rust PR #19891 was merged. You can, however, cast the functions to the corresponding function pointer type with the as operator.

call_both(foo as fn(u32) -> bool, bar as fn(u32) -> bool);

It's also valid to cast only the first of the functions: the cast will be inferred on the second, because both functions must have the same type.

call_both(foo as fn(u32) -> bool, bar);

Function item vs. function pointer

When you refer to a function by its name, the type you get is not a function pointer (e.g. fn(u32) -> bool). Instead, you get an a zero-sized value of the function's item type (e.g. fn(u32) -> bool {foo}).

The value is zero-sized because it doesn't store the actual function pointer. The type perfectly identifies the function, so there is no need to store actual data in the type. This has several advantages, mostly about easier optimizations. A function pointer is like you would expect from other languages: it stores an address to the function.

A function pointer refers to the function via stored address; a function item refers to the function via type information.

A function item can be coerced to a function pointer in many situations, for example: as argument to a function and in let _: fn(u32) -> bool = foo; statements. Additionally, you can explicitly cast a function item to a function pointer: foo as fn(u32) -> bool.

You can read more about this topic in the reference on function items, function pointers and coercion.



Solution to your problem

In your case, the compiler isn't smart enough to figure out that you want the function pointers from your foo and bar instead of function items. When you call call_both(foo, bar) the compiler sets the generic type F to be fn(u32) -> bool {foo}, because that's the type of the first argument. And then it complains that the second argument doesn't have the same type.

You can fix that by explicitly specifying the F parameter:

call_both::<fn(u32) -> bool>(foo, bar);
call_both::<fn(_) -> _>(foo, bar);       // <-- even this works

After specifying the type, the compiler can correctly coerce the arguments to a function pointer. You could also as-cast the first argument to fn(u32) -> bool explicitly.

You can fix your second example by explicitly stating the function pointer type, too:

let mut x: fn(u32) -> bool = foo;
x = bar;

In general: specifying the function pointer type somewhere to trigger the coercion will work.

Tags:

Rust