pub struct Foo{}
pub trait T1{}
pub trait T2{}
pub trait T3{}
impl T1 for Foo{}
impl T2 for Foo{}
impl T3 for Foo{}
fn main() {
let x = Rc::new(Foo{});
let t1: Rc<dyn T1> = x.clone();
let t2: Rc<dyn T2> = x.clone();
let t3: Rc<dyn T3> = x.clone();
}
Now, the count on object x is 4 right? Furthermore, the t1, t2, t3 have vtable handles -- where are these vtable handles stored?
If the handles are stored inside the object Foo, they are going to clobber each other. If they are not stored inside the object Foo, where are they stored?
They [vtables] are stored in the Rc handles, alongside with the pointer. The Foo itself (and refcounts) is intact. This concept is called "fat pointer". Check out this cheat sheet. It doesn't explain Rc<dyn Trait> directly, but it explains Rc<T> and Box<dyn Trait>.
Does this mean that Box<T> and Box<dyn Trait> have different size? I used to think that Box<T> was always the same size regardless of T because T was stored on the heap, so Box<T> was just one pointer.
However, it now appears that Box<T> is always 1 ptr while Box<dyn T> is 2 ptrs? It's almost as if "struct Box" behaves drastically differently depending on whether it's given a Type or a Trait.
I'm still struggling understanding this 'fat pointer.'
Is this also why we can have Rc<dyn A> but not Rc<dyn A + B>, because in a "fat pointer", we only have 1 vtable slot and thus can't say "this object satisfies trait A & trait B" ?
@anon80458984 since sometimes actual code can help make things clearer (although sometimes it doesn't), here is a PoC of manually implementing dyn Trait functionality:
Click to expand
#[cfg(FALSE)] /// This "becomes"
pub
trait T1 {
fn method (&self, x: usize) -> bool;
fn name (&self) -> String;
}
pub use compiler_generated::Trait as T1;
mod compiler_generated {
use ::std::{mem, ptr};
pub type Something = u8; // actual type is "erased"
pub
trait Trait : CompilerExt {
fn method (&self, x: usize) -> bool;
fn name (&self) -> String;
fn coerce_to_box_dyn (self: Box<Self>) -> BoxDynTrait
where
Self : Sized,
{
BoxDynTrait {
this: Box::into_raw(self).cast(),
vtable: &Self::VTABLE,
}
}
}
pub
trait CompilerExt {
const VTABLE: VTable;
}
pub
struct VTable {
size: usize,
align: usize,
drop_in_place: unsafe fn (*mut Something),
method: unsafe fn (*const Something, x: usize) -> bool,
name: unsafe fn (*const Something) -> String,
}
impl<T : Trait> CompilerExt for T {
const VTABLE: VTable = VTable {
size: mem::size_of::<T>(),
align: mem::align_of::<T>(),
drop_in_place: unsafe { mem::transmute(
ptr::drop_in_place::<T> as *const ()
)},
method: unsafe { mem::transmute(
<T as Trait>::method as *const (),
)},
name: unsafe { mem::transmute(
<T as Trait>::name as *const (),
)},
};
}
pub
struct BoxDynTrait {
this: *mut Something,
vtable: &'static VTable,
}
impl Trait for BoxDynTrait {
#[inline(always)]
fn method (&self, x: usize) -> bool
{
unsafe {
(self.vtable.method)(self.this, x)
}
}
#[inline(always)]
fn name (&self) -> String
{
unsafe {
(self.vtable.name)(self.this)
}
}
}
impl Drop for BoxDynTrait {
fn drop (self: &mut Self)
{
// drop(Box::<??>::from_raw(self.this))
unsafe {
(self.vtable.drop_in_place)(self.this);
if self.vtable.size != 0 {
extern crate alloc as alloc_crate;
use alloc_crate::alloc;
alloc::dealloc(
self.this,
alloc::Layout::from_size_align_unchecked(
self.vtable.size,
self.vtable.align,
),
)
}
}
}
}
}