1

As Fn-like traits can be implemented multiple times, one can not do this:

trait Callable {
    fn call_me(&self);
}

impl<F, T> Callable for F
where
    F: Fn(T),
    T: Default,
{
    fn call_me(&self) {
        self(T::default())
    }
}

I'd like to do something like this, so I tried using trait objects as I can implement traits for them:

impl<T> Callable for dyn Fn(T)
where
    T: Default,
{
    fn call_me(&self, _: &Env) {
        self(T::default())
    }
}

This works, but I want to store all kinds of Callable for a callback system. My first and best attempt was to use a Vec<Box<dyn Callable>>, but it seems that trait objects can not be converted to trait objects of traits they implement.

trait Callable {
    fn call_me(&self);
}

impl<T> Callable for dyn Fn(T)
where
    T: Default,
{
    fn call_me(&self) {
        self(T::default())
    }
}

impl<T> Callable for &dyn Fn(T)
where
    T: Default,
{
    fn call_me(&self) {
        self(T::default())
    }
}

fn main() {
    let f = |n: i32| println!("n = {}", n);

    // fine, obviously
    f(i32::default());

    // also fine
    let bf = Box::new(f) as Box<dyn Fn(i32)>;
    bf(i32::default());

    // works if Callback implemented for &dyn Fn(T)
    // but only works on references now
    let bfc = &bf.as_ref() as &dyn Callable;
    bfc.call_me();

    // does not work
    let callbacks: Vec<Box<dyn Callable>> = vec![bf as Box<dyn Callable>];
}

This gives me:

error[E0605]: non-primitive cast: `std::boxed::Box<dyn std::ops::Fn(i32)>` as `std::boxed::Box<dyn Callable>`
  --> src/main.rs:39:50
   |
39 |     let callbacks: Vec<Box<dyn Callable>> = vec![bf as Box<dyn Callable>];
   |                                                  ^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object

I don't understand. Trait objects are types, I implemented a Callable for all Fn(T) trait objects, and you can normally coerce from a specific implemented trait object.

  • Why is it not working?
  • Is there a workaround?
  • Is there a reason why Fn traits parameter types are generic and not associated?

Side note

I want this kind of system in order to store any closure (not only functions) whose parameters can be fetched from an environment, regardless of the number and types of parameters.

trait FromEnv {
    fn from_env(env: &Env) -> Self;
}

trait Callable {
    fn call_me(&self, env: &Env);
}

impl<T> Callable for Fn(T)
where
    T: FromEnv,
{
    fn call_me(&self, env: &Env) {
        self(T::from_env(env))
    }
}

// same kind of impl for Fn(T, U, ..., Z)

I know I can:

  • allow closures which take &Env and let the user handle "extraction".
  • use a macro to create my own custom closure type which directly implements Callable.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lilymonade
  • 465
  • 4
  • 11
  • 2
    tl;dr the duplicate: you can't do this because `&dyn Fn...` is already a fat pointer, and you need both parts (data pointer and vtable pointer) to implement `Callable`. As for a "workaround", I haven't given much thought to this idea, but maybe you could create a `struct Callback { _marker: PhantomData, f: F }` and use that to wrap closures, instead of using the bare closure; that would give you a type parameter `T` that you can specify at the site where you need to cast to `dyn Callable`. – trent Sep 09 '20 at 14:25
  • Oh yes of course ! The pointed vtable does not have any way to know where the `Callable` vtable is... Ok so I can still use double box as boxes are sized ... but performances ... XD Thanks a lot ! How do I set this as "already answered" ? Oh I like your workaround, didnt read well the first time. – Lilymonade Sep 09 '20 at 14:25
  • There should be a button or a link somewhere, either at the top of the question or at the bottom... Stack Overflow has changed things recently, so I'm not sure anymore. It may be labeled "close". (Poor naming, maybe. Questions closed as duplicates are not deleted; they are kept to help other people find the same answers quicker.) – trent Sep 09 '20 at 14:30

0 Answers0