Your Box can only get implicitly casted to a Box<dyn Trait> if it's a single layer. If it's nested (such as within an Option), you need to explicitly cast it beforehand with as. Try:
let val = Some(Box::new(Struct {field: true}) as Box<_>);
To let the inference engine know that you don't want an Option<Box<Struct>>, and instead something else.
kpreid yeah, I guess I can do that with option, but I have a similar situation with another type (Rc, don't think there's a way to .map an Rc like that).
Why would the compiler make me jump around like this? If it would be a Box<Struct> struct it would have been fine, why is that not the case for any other type holding a Box<Struct> like Option and Rc?!
Because a Box of dyn is a different data structure than a Box of a regular type — it has an extra pointer. There has to be specific support for the conversion.
You can see the list of types that you can coerce this way at the trait impls for CoerceUnsized. Notice they're all pointer and cell types — not other kinds of containers.
Type coercions are inserted at the same time as the compiler does type checking. Basically, the compiler inserts a coercion when it sees a value of known type X being stored in a place (e.g. a local, a temporary, a call argument, a return value) of a known type Y, where it can see that X and Y are different.
The coercion of Box<Struct> to Box<dyn Trait> is simple but not trivial; It requires adding a vtable. If you had some arbitrary generic struct, then GenericStruct<Box<Struct>> and GenericStruct<Box<dyn Trait>> could potentially have totally different layouts, so the compiler can't easily insert coercions for arbitrary nested types like this. Instead, you must coerce the Box itself before placing it inside.
Without something like as _ to introduce another unknown, the compiler will decide that the type of the box is Box<Struct> before it finishes checking the rest of the function, and the opportunity for inserting a coercion is lost. This is just how the type checker works, and it's what allows you to normally call methods of Struct on Box::new(the_struct) without having to declare it explicitly as Box<Struct>.
To formulate the "coercions are inserted at type-check time" part differently: Box<dyn Trait> is not a supertype of Box<Struct implementing Trait>. It's a concrete type (for each trait) that happens to implement the trait by forwarding to the implementation to another concrete type. It is not a superclass, in particular, although the dynamic dispatch mechanism is necessarily similar to how inheritance is implemented via vtables.
To draw a more extreme analogue: you can represent an integral number either as e.g. u64 or as a String containing the decimal expansion of the number. Both can hold the same information and you can use them for the same purpose (although doing so is neither elegant nor practical). There is a conversion between them (you can to_string and integer and parse() a string), but this doesn't make either one a subtype or supertype of the other.
I planned on having two structs existing simultaneously with an Rc to a trait object, one with referring to it with it's type and one referring to it as a trait object, from your answers I understand that this is not going to be possible since they might have different memory layouts, is that true? If so, is there any other way I can have both of them share an Rc to the same trait object?
use std::rc::Rc;
struct Shared;
trait Trait {}
impl Trait for Shared {}
struct StrictParent {
child: Rc<Box<Shared>>,
}
struct LenientParent {
child: Rc<Box<dyn Trait>>,
}
fn main() {
let child = Rc::new(Box::new(Shared));
let strict_parent = StrictParent {
child: Rc::clone(&child)
};
let lenient_parent = LenientParent {
child
};
}
Option<dyn Trait> is not a well-formed type though. You could use Option<Rc<dyn Trait>> (and Option<Rc<Shared>> if that fits your use-case. Or you could change Trait’s API and the impl Trait for Shared in a way that Trait is implemented by Option<Shared> instead of Shared, and then use Rc<Option<Shared>> and Rc<dyn Trait>. [Or use an additional trait; and with a blanket impl based on Trait… it all depends on what kinds of operations you actually require.]
But then again, there's no good reason for that double indirection, either… Option<Rc<dyn Trait>> expresses the same thing (bonus: if the optional is none, then it doesn't allocate). Playground.
let child: Option<Rc<Shared>> = Some(Rc::new(Shared));
let strict_parent = StrictParent {
child: child.clone(),
};
let lenient_parent = LenientParent {
child: child.map(|rc| rc as _),
};
Unfortunately neither of the options you proposed would work in my case, but I think that I might be able to rewrite the whole module so that only one struct needs to hold the child and the other one would call methods of the child through the parent.
Really appreciate you holding my hand like this, I've certainly learned a lot from you today!