Search code examples
rustcallbackclosuresvtabledynamic-dispatch

How to pass closure to dyn trait object


I want a dyn reference to a trait object, on which I have a method, taking a closure as argument:

trait DynTrait {
    fn dyn_method(&mut self, closure: impl FnMut(&str) + 'static);
}

// ...

let dyn_object: &mut dyn DynTrait = ...;

The closure can live longer than the call to dyn_method, so it will be stored as some form of Box/Rc/... .

This example does not compile with error:

error[E0038]: the trait `DynTrait` cannot be made into an object

which makes sense, since impl is just syntactic sugar for a generic function and a trait with a generic function cannot be made into an object.

One way to solve that is to replace impl with &dyn (The user has to specify &mut now):

trait DynTrait {
    fn dyn_method(&mut self, closure: &mut (dyn FnMut(&str) + 'static));
}

But in this case, we don't get ownership of the closure and we can't make it into a Box/Rc/... . We could make the &dyn ref 'static, but this defeats the whole purpose of a closure and we could just use a function pointer.

The other way is to pass in a Box/Rc/... directly:

trait DynTrait {
    fn dyn_method(&mut self, closure: Box<dyn FnMut(&str) + 'static>);
}

Which works fine, but now the implementation detail leaks to the user and makes it more verbose for him, because he always has to pass the closure to Box::new(). Also, sometimes Rust has trouble automatically inferring the type of the Closures arguments, which makes it even more verbose to the user.

Playground


Solution

  • You can work around the issue by defining the trait like in your last example with a Box<dyn Fn()> as a parameter and then add an extension trait that implements the method with your original syntax to everything that implements DynTrait:

    trait DynTrait {
        fn dyn_method_boxed(&mut self, closure: Box<dyn FnMut(&str) + 'static>);
    }
    
    trait DynTraitExt {
        fn dyn_method(&mut self, closure: impl FnMut(&str) + 'static);
    }
    
    impl<T: ?Sized + DynTrait> DynTraitExt for T {
        fn dyn_method(&mut self, closure: impl FnMut(&str) + 'static) {
            self.dyn_method_boxed(Box::new(closure))
        }
    }
    

    I've renamed the method that takes a Box since otherwise it's name clashes with the on from the extension trait.

    This approach also let's the user pass in their Box<dyn Fn()> directly if they already have it.

    Playground