This is a correct behavior, even if it is somewhat unfortunate.
In the first case we have this:
let mut pond: Vec<Box<Quack>> = Vec::new();
let duck: Box<Duck> = Box::new(Duck);
pond.push(duck);
Note that push()
, when called on Vec<Box<Quack>>
, accepts Box<Quack>
, and you're passing Box<Duck>
. This is OK - rustc is able to understand that you want to convert a boxed value to a trait object, like here:
let duck: Box<Duck> = Box::new(Duck);
let quack: Box<Quack> = duck; // automatic coercion to a trait object
In the second case we have this:
let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new();
let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck)));
lake.push(mallard);
Here push()
accepts Rc<RefCell<Box<Quack>>>
while you provide Rc<RefCell<Box<Duck>>>
:
let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck)));
let quack: Rc<RefCell<Box<Quack>>> = mallard;
And now there is a trouble. Box<T>
is a DST-compatible type, so it can be used as a container for a trait object. The same thing will soon be true for Rc
and other smart pointers when this RFC is implemented. However, in this case there is no coercion from a concrete type to a trait object because Box<Duck>
is inside of additional layers of types (Rc<RefCell<..>>
).
Remember, trait object is a fat pointer, so Box<Duck>
is different from Box<Quack>
in size. Consequently, in principle, they are not directly compatible: you can't just take bytes of Box<Duck>
and write them to where Box<Quack>
is expected. Rust performs a special conversion, that is, it obtains a pointer to the virtual table for Duck
, constructs a fat pointer and writes it to Box<Quack>
-typed variable.
When you have Rc<RefCell<Box<Duck>>>
, however, rustc would need to know how to construct and destructure both RefCell
and Rc
in order to apply the same fat pointer conversion to its internals. Naturally, because these are library types, it can't know how to do it. This is also true for any other wrapper type, e.g. Arc
or Mutex
or even Vec
. You don't expect that it would be possible to use Vec<Box<Duck>>
as Vec<Box<Quack>>
, right?
Also there is a fact that in the example with Rc
the Rcs created out of Box<Duck>
and Box<Quack>
wouldn't have been connected - they would have had different reference counters.
That is, a conversion from a concrete type to a trait object can only happen if you have direct access to a smart pointer which supports DST, not when it is hidden inside some other structure.
That said, I see how it may be possible to allow this for a few select types. For example, we could introduce some kind of Construct
/Unwrap
traits which are known to the compiler and which it could use to "reach" inside of a stack of wrappers and perform trait object conversion inside them. However, no one designed this thing and provided an RFC about it yet - probably because it is not a widely needed feature.