Implement trait for all function pointers

I am trying to implement a trait for all function pointers. I've tried to do this by both implementing the trait for fn() as well as F: Fn() but am running into limitations with both options.

A simplified example is:

trait A { }
trait C { }

impl A for fn() { }
impl A for &fn() { }

impl<P> A for fn(&P) { }
impl<P> A for &fn(&P) { }

impl<F> C for F where F: Fn() { }
// impl<F, P> C for F where F: Fn(&P) { } -- unconstrained type parameter

fn my_func() { }
fn my_func_with_p(_: &usize) { }

fn accept_a<F: A>(_: &F) { }
fn accept_dyn_a(_: &dyn A) { }

fn accept_c<F: C>(_: &F) { }
fn accept_dyn_c(_: &dyn C) { }

fn main() {
    accept_a(&(my_func as fn()));
    accept_a(&(my_func_with_p as fn(&usize)));
    // accept_dyn_a(&my_func); -- trait bound not satisfied
    
    accept_c(&my_func);
    accept_dyn_c(&my_func);
}

And the playground is available at: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=889ed167fbd2dad717b6e6a63a178764

  1. If I implement the trait for fn() I have to cast the value, and can't use it as an argument for a parameter of &dyn A.
  2. If I implement the trait for Fn(), I don't have to worry about casting the value and can use it as &dyn C, but can't implement it for functions with an argument since the type parameter is unconstrained.

Am I doing something wrong and/or missing some alternative option?

What behavior/interface is available on all function pointers and closures with arbitrary number and type of arguments? That is, if you succeed in implementing this trait MyFn, and I give you a Box<dyn MyFn>, what do you expect to be able to do with it?

(I'm not aware of any way to be generic over functions and closures with different type signatures.)

Good question, so this is a better example of what I am wanting to do:

use std::any::Any;

struct Context {
    arg: Box<dyn Any>,
}

trait Runnable {
    fn caller(&self) -> &str {
        std::any::type_name::<Self>()
    }

    fn exec(&self, ctx: &Context);
}

trait Callable<P>: Runnable {
    fn call(&self, arg: &P);
}

impl<P: Any> Runnable for fn(&P) {
    fn exec(&self, ctx: &Context) {
        println!("caller :: {}", self.caller());

        let arg = &ctx.arg.downcast_ref::<P>().unwrap();
        (self)(arg)
    }
}

impl<P: Any> Callable<P> for fn(&P) {
    fn call(&self, arg: &P) {
        (self)(arg)
    }
}

impl<F> Runnable for F
where
    F: Fn(),
{
    fn exec(&self, _: &Context) {
        println!("caller :: {}", self.caller());
        (self)()
    }
}

fn my_runnable() {}
fn my_callable(_: &usize) {}

fn to_runnable<P: Any, F: Callable<P> + 'static>(f: F) -> Box<dyn Runnable> {
    Box::new(f)
}

fn run_runnable(f: impl Runnable, ctx: &Context) {
    f.exec(ctx);
}

fn main() {
    let runner = to_runnable(my_callable as fn(&usize));

    let ctx = Context {
        arg: Box::new(0usize),
    };

    runner.exec(&ctx);
    // run_runnable(my_callable, &ctx);

    run_runnable(my_runnable, &ctx);   
}

playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6a4a99b1462215976f9a27a56c7d759b

So the primary benefits that I see for implementing Runnable for F: Fn() is that it makes calling it easier since there isn't any casting involved as well as giving a more accurate type_name. However, I can't implement the Runnable for F with generic P (F: Fn(P)).

@kylem the issue is that you seem to assume that the callable contract of Fn{,Mut,Once} seems to be unique (at most one) per type / implementor, which is not true (future-true, to be exact).

That is, in the future / in nightly, it is possible for a given type F, to be both Fn() and Fn(&P), as well as Fn(&Q), Fn(&R), etc.

See:

for an example.


So, to solve:

impl<F> C for F where F: Fn() { }
// impl<F, P> C for F where F: Fn(&P) { } -- unconstrained type parameter

you will need the trait C to be in some way, generic over (something that depends on) P.

That being said, then you'd have issues with "no arity" and "arity over a parameter with a higher-order lifetime" to be hard-to-impossible to abstract over.

So let's abandon the idea of a generic trait (some sub usage could be possible (&P or &Dummy), but then that wouldn't be a trait that could be used as a dyn, because of its generic parm).


The last option, then, is to use wrapper types, that allow to get back "unique implementors":

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.