Implementing traits on function pointers

Is it possible to make this compile? I would like to avoid this ugly "as FunctionType"

use_function() is supposed to take either a function pointer directly, or something that can be converted into a function pointer.

fn main() {
    println!("Hello, world!");
    use_function(Function::Default);
    use_function(default_function); // Is it possible to make this compile?
    use_function(default_function as FunctionType); // this works, but is ugly
}

fn use_function<F: IntoFunction>(format_function: F) {
    unimplemented!("use_function");
}

fn default_function() -> Result<(), std::io::Error> {
    unimplemented!("default_function");
}
fn other_function() -> Result<(), std::io::Error> {
    unimplemented!("other_function");
}

pub trait IntoFunction {
    fn format_function(self, other: bool) -> FunctionType;
}

enum Function {
    Default,
    Custom(FunctionType, FunctionType),
}
impl IntoFunction for Function {
    fn format_function(self, other: bool) -> FunctionType {
        if other {
            match self {
                Self::Default => other_function,
                Self::Custom(_, method2) => method2,
            }
        } else {
            match self {
                Self::Default => default_function,
                Self::Custom(method1, _) => method1,
            }
        }
    }
}

pub type FunctionType = fn() -> Result<(), std::io::Error>;

impl IntoFunction for fn() -> std::result::Result<(), std::io::Error> {
    fn format_function(self, _other: bool) -> FunctionType {
        self
    }
}

// impl IntoFunction for FunctionType {
//     fn format_function(self, _other: bool) -> FunctionType {
//         self
//     }
// }


(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `fn() -> std::result::Result<(), std::io::Error> {default_function}: IntoFunction` is not satisfied
 --> src/main.rs:4:18
  |
4 |     use_function(default_function); // FIXME
  |                  ^^^^^^^^^^^^^^^^ the trait `IntoFunction` is not implemented for `fn() -> std::result::Result<(), std::io::Error> {default_function}`
...
8 | fn use_function<F: IntoFunction>(format_function: F) {
  |                    ------------ required by this bound in `use_function`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

So the problem you hit here is that function types are different from function pointer types. The error message talks about the type “fn() -> std::result::Result<(), std::io::Error> {default_function}” which is a compiler-errors-message way of saying “the type of default_function which happens to be a function without parameters returning Result<(), io::Error>”.

It is important to know in this context that each and every function in Rust has it’s own unique type which has the effect that passing a function to something like Iterator::map will get specialized and properly inlined. Functions are similar to non-capturing closures in this way. Another similarity between the two is that both can be coerced into function pointers, e.g. with as.

It’s a bit unfortunate that there’s currently no trait that expresses “type T can be coerced into type S”. A possible workaround, using unsafe code, could be to check for a zero-sized and Copy type that implements Fn. Playing around in a playground got me something like this. This could be used to implement your trait. It seems difficult to also still support function pointers without specialization.


Here’s how to integrate my playground into yours (I haven’t fixed warnings on public/private issues): (LINK)


Edit: One should probably also add a F: 'static bound to the ZeroSizedFunction implementation for good measure. I’m not sure if it might me unsound otherwise. Also, with 'static in place it might become possible to use TypeId to also handle the function-pointer case. Edit2: It would be possible if TypeId::of was a const fn. Without that I don’t see how to keep nice compile-time errors for trying to use non-conforming types. (It’s still possible to write an implemenation supporting both function types and function pointer types that only creates a runtime panic if used with closures that capture anything (relevant)).

1 Like

If you're willing to use the Fn traits instead of function pointers, you can use associated types on the IntoFunction trait instead. But be aware that approach will not allow you to convert the resulting functions back into function pointers.