5

I'm a bit stumped on how to get this working, I've cut it down from the real thing. I wrote a trait:

pub trait Renderable<F: Fn(&PropertyTags)> {
    fn set_property_changed_callback(&mut self, callback: Option<F>);
}

Which the 'child' parameter of add_child is restricted by and PropertyTags is just an enum. I've included mock implementations of the type of child to demonstrate my usage:

pub struct Child<F: Fn(&PropertyTags)> {
    property_changed_callback: Option<F>,
}

impl<F: Fn(&PropertyTags)> Renderable<F> for Child<F> {
    fn set_property_changed_callback(&mut self, callback: Option<F>) {
        self.property_changed_callback = callback;
    }
}

Then these would be used as:

pub fn add_child<REND, C>(&mut self, child: &mut REND)
    where C: Fn(&PropertyTags),
        REND: Renderable<C>
{
    let tc = Some(|property_tag: &PropertyTags|{
            });

    child.set_property_changed_callback(tc);
}

I'm getting the error:

child.set_property_changed_callback(tc);
   |                                ^^ expected type parameter, found closure
   |
   = note: expected type `std::option::Option<C>`
   = note:    found type `std::option::Option<[closure@src/rendering/mod.rs:74:31: 76:18]>`
   = help: here are some functions which might fulfill your needs:
 - .take()
 - .unwrap()

I've setup a minimal playground example which reproduces the issues here: https://play.rust-lang.org/?gist=bcc8d67f25ac620fe062032d8737954b&version=stable&backtrace=0

Simon Jackson
  • 442
  • 7
  • 17
  • I'm sorry if this is a duplicate, I've read a bunch of similar questions on SO but none have helped me fix it. – Simon Jackson Jan 15 '17 at 17:51
  • You would need to include more code for us to be able to test it. In the meantime, have you seen [this question](http://stackoverflow.com/questions/36414576/returning-a-closure-from-a-trait-method-involving-generics-in-rust)? You probably need to wrap the closure in a `Box`. – ljedrz Jan 15 '17 at 18:15
  • I wasn't sure it was the same issue since they were trying to return a closure. I was hoping to avoid Boxing it, I was under the impressesion this would make it dynamically dispatched? – Simon Jackson Jan 15 '17 at 19:30

1 Answers1

4

The problem is that add_child claims to accept any Renderable<C>, where C can be any type that implements Fn(&PropertyTags), but then the function tries to give it a specific closure type that might not be the same as C.

In order for this to work, add_child's signature should look like this:

pub fn add_child<REND>(&mut self, child: &mut REND)
    where REND: Renderable<AddChildCallback>

where AddChildCallback is the name of a concrete type (that implements Fn(&PropertyTags)).

The difficulty here is that on one hand, closure types don't have a name you can use in your Rust code, and on the other hand, implementing Fn manually is unstable, so it requires a nightly compiler.

I'll also note that by making the callback type a type parameter, a Renderable cannot be assigned a callback of a different type after a first callback has been set, as the first callback will determine the concrete type for the Renderable. This might be fine for your usage, I just wanted to make sure you're aware of that.

If you want a solution that works on stable compilers (as of Rust 1.14.0), then you'll have to box the callback. add_child's signature would then look like this:

pub fn add_child<REND>(&mut self, child: &mut REND)
    where REND: Renderable<Box<Fn(&PropertyTags)>>

Here is an updated playground link with an example implementation of Fn. Note that the parameters for call, call_mut and call_once are passed as a tuple, as is required by the trait definition. The code is reproduced below for completeness:

struct RenderableCallback {

}

impl<'a> Fn<(&'a PropertyTags,)> for RenderableCallback {
    extern "rust-call" fn call(&self, args: (&'a PropertyTags,)) -> Self::Output {

    }
}

impl<'a> FnMut<(&'a PropertyTags,)> for RenderableCallback {
    extern "rust-call" fn call_mut(&mut self, args: (&'a PropertyTags,)) -> Self::Output {

    }
}

impl<'a> FnOnce<(&'a PropertyTags,)> for RenderableCallback {
    type Output = ();
    extern "rust-call" fn call_once(self, args: (&'a PropertyTags,)) -> Self::Output {
    }
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 1
    Just to clarify, its possible to assign a different AddChildCallback but not a different concrete implementation of Fn(&PropertyTags)? – Simon Jackson Jan 17 '17 at 22:03