What is the exact type of a function?

Consider this example

trait MyTrait{
   fn call(self);
}
impl MyTrait for fn(i32)->(){
    fn call(self){
       
   }
}
fn fun(i32){
}
fn main(){
   let f = fun;
   f.call(); // error
   (f as fn(i32)).call(); // OK
}

Isn't that the type of fun is fn(i32)->()? why cannot find the trait method call for f? Instead, we need to do the conversion to make the call be found for the receive expression.

Each function in Rust has its unique (zero-sized) anonymous type (or type constructor, for generic functions), i. e. you cannot name its type directly in source code, perhaps comparable to the types of closures or async {} blocks.

This zero-sized type can be implicitly coerced into the type fn(…) -> … with the right signature. This is the type of function pointers, which is (usually) as large as a pointer and is shared between all functions of the same signature. It's similar to Box<dyn Fn(…) -> …> in that calling it will be a so-called “dynamic” function call, as opposed to a “static” function call; in the latter case, the point in code is known at compile time, and optimizations such as inlining are more straightforward for the compiler to pull off.

9 Likes

If you want to add a method, via trait, to all “functions” including function items and closures, you can implement your trait generically for e. g. all types F: Fn(i32), or maybe even for all FnOnce… - on the other hand, if you need some of the abilities of fn pointers such as that you can copy them or that they are thread-safe, then you could add further bounds like Send, Sync, and/or Copy, in case you need them for implementing your method. Some things, like the ability to turn it into fn pointer, are unfortunately not possible to express as a trait bound.

3 Likes

I found a way to provoke the compiler to emit a more helpful error message:

   = note: expected fn item `fn(_) {fun}`
              found fn item `fn(_) {fun2}`
   = note: different `fn` items always have unique types, even if their signatures are the same
   = help: change the expected type to be function pointer `fn(i32)`
   = help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `fun as fn(i32)`

Here is the code:

trait MyTrait{
   fn foo(self);
}
impl MyTrait for fn(i32)->(){
    fn foo(self) {}
}
fn fun(_: i32) {}
fn fun2(_: i32) {}
fn main(){
   let mut v = Vec::new();
   //let mut v: Vec<fn(i32)> = Vec::new();
   v.push(fun);
   v.push(fun2);
   v[0].foo();
}

(Playground)

2 Likes

Nice.