Difference between `fn()` and `fn() -> ()`

Greetings!

This is a compiler bug, or I'm missing something?

struct Callback<T>(fn() -> T);

impl<T> Callback<T> {
    pub fn new<F>(callback: F) -> Self
    where
        F: Into<fn() -> T>,
    {
        Self(callback.into())
    }

    pub fn call(&self) -> T {
        self.0()
    }
}

fn hello_world() {
    println!("Hello World!");
}

fn main() {
    let callback = Callback::new(hello_world);
    // This work fine!
    //let callback = Callback::new(hello_world as fn() -> ());

    callback.call();
}
/Users/asinotov/.cargo/bin/cargo build --color=always --message-format=json-diagnostic-rendered-ansi
   Compiling playground3 v0.1.0 (/Users/asinotov/CLionProjects/playground3)
error[E0277]: the trait bound `fn() -> _: From<fn() {hello_world}>` is not satisfied
  --> src/main.rs:21:34
   |
21 |     let callback = Callback::new(hello_world);
   |                    ------------- ^^^^^^^^^^^ the trait `From<fn() {hello_world}>` is not implemented for `fn() -> _`
   |                    |
   |                    required by a bound introduced by this call
   |
   = note: required because of the requirements on the impl of `Into<fn() -> _>` for `fn() {hello_world}`
note: required by a bound in `Callback::<T>::new`
  --> src/main.rs:6:12
   |
4  |     pub fn new<F>(callback: F) -> Self
   |            --- required by a bound in this
5  |     where
6  |         F: Into<fn() -> T>,
   |            ^^^^^^^^^^^^^^^ required by this bound in `Callback::<T>::new`

error: aborting due to previous error

I think this is a known weakness, I don't remember the issue though.
I think @quinedot had pointed it out in some post.

I think ( and I am very far from being an expert ) that when working with function pointers type inference often doesn't work without some extra type hints, and this is probably the problem here.

Edit: well, perhaps it is more accurate to say you often need to use "coercion". I think the explanation is here: Function pointer types - The Rust Reference

They can be created via a coercion from both function items and non-capturing closures.
1 Like

Box<dyn Fn() -> T> have same problem.
P.S. I'm implementing callback system with enum with 2 options: pointer and boxed closure.

Why do you have the constructor F: Into<fn() -> T>-based? What's your use-case where directly passing the fn() -> T is too inconvenient? Removing this Into step will make this particular use-case more convenient (i. e. it will probably just work; though I haven't tested it yet).

Note that AFAICT the compiler is not complaining about the difference between fn() and fn() -> () (there is no difference between those) but the difference between a function pointer fn() and the function type of hello_world[1] (a zero-sized type that can be coerced into a function pointer but is not a function pointer, and thus doesn't implement Into<fn()>; yet this way of making the fn new generic precludes implicit coercion at this point.)


  1. which the compiler error message writes as “fn() {hello_world}” ↩ī¸Ž

6 Likes
pub(crate) enum CallbackFn<T> {
    Pointer(fn() -> T),
    Boxed(Box<dyn Fn() -> T>),
}

The closest thing I can think of is #95132, but I agree with @steffahn -- this is about the distinction between impl Into and the ability to coerce. The latter does not imply the former.

Ah, well then you could consider just always using Box<dyn Fn -> T>. If the typical use-case for function pointers would have been to store some non-capturing closures or function items, note that boxing those does not even involve any allocation (since they're zero-sized) and the overhead when called, compared to function pointers, is minimal.[1]

Only if you already have some function pointers then turning those into Box<dyn Fn> would need to allocate, so unless that's an important use-case for you...

If you don't plan on regularly paying in pre-boxed functions, you could also make your constructor take a F: impl Fn() -> T and call Box::new inside it. The downside is that passing an already boxed Box<dyn Fn() -> T> would then result in double-boxing; even if this is a problem for you, there'd then still be the option to just offer two constructors; one for convenience taking F: impl Fn() -> T and another constructor taking Box<dyn Fn() -> T> while avoiding the redundant extra boxing.


  1. There's some overhead in the first place only because looking up the function pointer in the vtable is cheap, but it is an additional step. ↩ī¸Ž

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.