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.
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.