I'm having trouble understanding the rules about traits in algebraic data types. Here's a simplified example:
use std::rc::Rc;
use std::cell::RefCell;
trait Quack {
fn quack(&self);
}
struct Duck;
impl Quack for Duck {
fn quack(&self) { println!("Quack!"); }
}
fn main() {
let mut pond: Vec<Box<Quack>> = Vec::new();
let duck: Box<Duck> = Box::new(Duck);
pond.push(duck); // This is valid.
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); // This is a type mismatch.
}
The above fails to compile, yielding the following error message:
expected `alloc::rc::Rc<core::cell::RefCell<Box<Quack>>>`,
found `alloc::rc::Rc<core::cell::RefCell<Box<Duck>>>`
(expected trait Quack,
found struct `Duck`) [E0308]
src/main.rs:19 lake.push(mallard);
Why is it that pond.push(duck)
is valid, yet lake.push(mallard)
isn't? In both cases, a Duck
has been supplied where a Quack
was expected. In the former, the compiler is happy, but in the latter, it's not.
Is the reason for this difference related to CoerceUnsized
?
This is a correct behavior, even if it is somewhat unfortunate.
In the first case we have this:
Note that
push()
, when called onVec<Box<Quack>>
, acceptsBox<Quack>
, and you're passingBox<Duck>
. This is OK - rustc is able to understand that you want to convert a boxed value to a trait object, like here:In the second case we have this:
Here
push()
acceptsRc<RefCell<Box<Quack>>>
while you provideRc<RefCell<Box<Duck>>>
: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 forRc
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 becauseBox<Duck>
is inside of additional layers of types (Rc<RefCell<..>>
).Remember, trait object is a fat pointer, so
Box<Duck>
is different fromBox<Quack>
in size. Consequently, in principle, they are not directly compatible: you can't just take bytes ofBox<Duck>
and write them to whereBox<Quack>
is expected. Rust performs a special conversion, that is, it obtains a pointer to the virtual table forDuck
, constructs a fat pointer and writes it toBox<Quack>
-typed variable.When you have
Rc<RefCell<Box<Duck>>>
, however, rustc would need to know how to construct and destructure bothRefCell
andRc
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
orMutex
or evenVec
. You don't expect that it would be possible to useVec<Box<Duck>>
asVec<Box<Quack>>
, right?Also there is a fact that in the example with
Rc
the Rcs created out ofBox<Duck>
andBox<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.Vladimir's answer explained what the compiler is doing. Based on that information, I developed a solution: Creating a wrapper struct around
Box<Quack>
.The wrapper is called
QuackWrap
. It has a fixed size, and it can be used just like any other struct (I think). TheBox
insideQuackWrap
allows me to build aQuackWrap
around any trait that implementsQuack
. Thus, I can have aVec<Rc<RefCell<QuackWrap>>>
where the inner values are a mixture ofDuck
s,Goose
s, etc.As an added convenience, I'll probably want to implement
Deref
andDefrefMut
onQuackWrap
. But that's not necessary for the above example.