I'm facing a compiler error when pushing an Fn (in my case) in a vector of Box<Box<dyn Fn>>.
My function is this one:
pub fn on_property_change<C>(&'static mut self, callback: C)
where
C: Fn(&str, &DBusValue) + 'static,
And when I write this:
let callback_double_box = Box::new(Box::new(callback));
self.callbacks.push(callback_double_box);
I get this error:
155 | pub fn on_property_change<C>(&'static mut self, callback: C)
| - found this type parameter
...
166 | self.callbacks.push(callback_double_box);
| ---- ^^^^^^^^^^^^^^^^^^^ expected `Box<Box<dyn Fn(&str, &DBusValue)>>`, found `Box<Box<C>>`
| |
| arguments to this method are incorrect
|
= note: expected struct `Box<Box<(dyn for<'a, 'b> Fn(&'a str, &'b DBusValue) + 'static)>>`
found struct `Box<Box<C>>`
= help: type parameters must be constrained to match other types
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
this line will make the compiler to infer the type of callback_double_box to be Box<Box<C>>. where C is the concrete type of the callback. you need to put the innerBox at a coercion site (unsized coercion from Box<C> to Box<dyn Fn()>), either:
eliminate the creation of the binding callback_double_box and inlining the boxing, e.g.
Thank you for the explanation which helped me a lot to understand. I still have one question though. Why is C considered the concrete type ? I didn't specify Sized.
This line automatically converts / casts to the correct type ?
generic type parameters are concrete types, as generics functions are monophized at callsite (in C++, I think it is often called template instantiation).
the Sized trait bound is a special case, it is implied for all generic type parameters (except the special Self type), unless you opt it out explicitly with the ?Sized bound.
yes.
this line gives the type checker necessary context so it knows the expected type of the expression, in other words, it is a coercion site.
in a bare let statement without explicit type ascription, there's not enough context for the type checker, so no coersion happens. after you bind the value to the variable callback_double_box, it is no longer possible to be casted to the correct type, because it is "double" boxed, since unsized coercion can only happen at one level of indirection.
What really matters isn't strictly about Sized or not, but that Box<C> is a different type than Box<dyn T>. C and dyn T are simply not the same type: C is a simple struct containing captures, with content depending on how much the closure captured (may be zero-sized), and needs only a simple thin pointer for a Box<C>. OTOH dyn T is an abstract thing that isn't stored in memory itself, but makes Box<dyn T> a pair of pointers, like (&'static Vtable<T>, Box<C>)
It may be easy to convert one to another, but it needs actual code generated to perform the conversion and change type's layout.
This is one of the few cases where Rust has implicit type conventions, and it's clumsy when mixed with type inference and generics that may not force the compiler to insert the conversion where you wanted.
In C you can implicitly cast short to long, but casting short* to long* is an entirely different thing which can't preserve the values. Same goes for casting Box<C> to Box<dyn T> and not Box<Box<C>> to Box<Box<dyn T>>.