Since you're working with boxed trait objects, you likely meant:
impl<T: ExpType> GeneralExpression for Box<dyn Expression<T>> { ... }
However, this doesn't allow casting (unsizing), say, a Box<dyn Expression<f64>>
to a Box<dyn GeneralExpression>
. It does allow something like the following though:
fn into_general(expr: Box<dyn Expression<T>>) -> Box<dyn GeneralExpression> {
Box::new(expr)
}
The difference is into_general
creates a new trait object.
As you probably know, trait objects are represented as two pointers (aka "fat pointer"): one to the data (i.e. the value) and the other to the vtbl that holds (amongst a few other things) that type's implementation of the trait.
A Box<dyn Expression<T>>
therefore has such a fat pointer. If you implement GeneralExpression
for Box<dyn Expression<T>>
, you now need a fat pointer whose data pointer points to the fat ptr of Box<dyn Expression<T>>
(because thats its own representation), and the vtbl pointer for the implementation of GeneralExpression
for this type. So you need a fat pointer to a fat pointer, essentially - the problem is Box<dyn Expression<T>>
is already storing its own fat pointer, and you can't "insert" another one directly into it (which is what casting, or unsizing, Box<dyn Expression<T>>
to Box<dyn GeneralExpression>
would involve). Therefore, if you wanted such a box, you'd need to create a new Box
wrapping the other one.
If you wanted to avoid the extra layer of boxes, you can try a trampoline (aka landing pad) approach - this is basically handwritten conversion infrastructure. Here is an example of what that might look like. The key bit is the into_boxed_general
method that's added, which takes a boxed Self
. What this ends up doing is, given a Box<dyn Expression<T>>
, it dispatches to the concrete type behind that fat pointer, while preserving the wrapper Box
, and that concrete type just echoes itself back. This gives the compiler the ability to unsize the box in-place, since it knows the concrete type inside the Box
. I'm not a MIR expert, but the MIR for DummyExpr::into_boxed_general()
looks like this:
fn <impl at src/main.rs:24:1: 32:2>::into_boxed_general(_1: std::boxed::Box<DummyExpr>) -> std::boxed::Box<dyn GeneralExpression>{
let mut _0: std::boxed::Box<dyn GeneralExpression>; // return place
let mut _2: std::boxed::Box<dyn GeneralExpression>;
let mut _3: std::boxed::Box<DummyExpr>;
bb0: {
StorageLive(_2); // bb0[0]: scope 0 at src/main.rs:30:9: 30:13
StorageLive(_3); // bb0[1]: scope 0 at src/main.rs:30:9: 30:13
_3 = move _1; // bb0[2]: scope 0 at src/main.rs:30:9: 30:13
_2 = move _3 as std::boxed::Box<dyn GeneralExpression> (Unsize); // bb0[3]: scope 0 at src/main.rs:30:9: 30:13
StorageDead(_3); // bb0[4]: scope 0 at src/main.rs:30:12: 30:13
_0 = move _2 as std::boxed::Box<dyn GeneralExpression> (Unsize); // bb0[5]: scope 0 at src/main.rs:30:9: 30:13
StorageDead(_2); // bb0[6]: scope 0 at src/main.rs:31:5: 31:6
return; // bb0[7]: scope 0 at src/main.rs:31:6: 31:6
}
}
The actual assembly for it is simply loading the new vtbl pointer:
<playground::DummyExpr as playground::Expression<f64>>::into_boxed_general:
leaq .L__unnamed_2(%rip), %rdx
movq %rdi, %rax
retq
Alright, all that said - do you really need Expression
and GeneralExpression
? It seems like you can make your life a lot easier by just sticking to GeneralExpression
and removing Expression
altogether.