0

I am trying to make a trait usable on top-level functions in Rust.

trait FnTrait {
    fn call(self);
}

impl FnTrait for fn() {
    fn call(self) {
        self()
    }
}

fn foo() {
    println!("Hello, World!")
}

fn main() {
    FnTrait::call(foo)
}

However the code below fails to compile with (Playground Link)

error[E0277]: the trait bound `fn() {foo}: FnTrait` is not satisfied
  --> <anon>:16:5
   |
16 |     FnTrait::call(foo)
   |     ^^^^^^^^^^^^^ the trait `FnTrait` is not implemented for `fn() {foo}`
   |
   = help: the following implementations were found:
             <fn() as FnTrait>
   = note: required by `FnTrait::call`

I found I can trick it into compiling by casting foo like so

FnTrait::call(foo as fn())

But it is annoying and some of the functions in my program are more complicated than foo. Any way to avoid the cast? Is my trait wrong somehow?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Maciej Goszczycki
  • 1,118
  • 10
  • 25

1 Answers1

3

Every function in Rust has its own type. As you can see, foo isn't a fn(), it's a fn() {foo}; sadly, this is not an actual type you can write in source, that's just a compiler message thing. The distinction exists to make it easier for the compiler to let you pass around functions as values whilst still being able to inline the calls.

The consequence is that named functions pointers cannot be turned into general function pointers without either a cast or a type hint. For example, this works:

fn foo() {
    println!("Hello, World!")
}

fn bar(f: fn()) {
    f()
}

fn main() {
    bar(foo)
}

However, I'm not aware of any way to leverage this to get the trait to work.

The only way forward is to stop trying to implement the trait for function pointers, and instead implement it for everything callable:

trait FnTrait {
    fn call(self);
}

impl<F> FnTrait for F where F: FnOnce() {
    fn call(self) {
        self()
    }
}

fn foo() {
    println!("Hello, World!")
}

fn main() {
    foo.call();
}

(Semi-relevant answer about the difference between Fn, FnMut and FnOnce.)

This will work for anything that's callable with that signature, including both functions and closures. The downside is that you can only have one such implementation. You can't implement this trait for any other signature.

One generic implementation, or many specific implementations and lots of manual casting. Pick your poison.


As an aside: there's no such thing as a "top level function" in Rust, at least not as a thing distinct from other kinds of functions. Functions are functions, no matter where they appear. Instance functions a.k.a. methods are still regular functions, it's just that their first argument is called "self".

DK.
  • 55,277
  • 5
  • 189
  • 162