What does the second field of a fat pointer of Struct<dyn Trait> point to?

In this code, the second field of the a fat pointer to *mut Container<dyn Hello> is something like 0x55bd42e4d9b8. My question is:

  • Is this a pointer to vtable of <i32 as Hello>?
trait Hello {
    fn hello(&self);
}

impl Hello for i32 {
    fn hello(&self) {
        println!("hello: {}", self);
    }
}

struct Container<T: ?Sized> {
    _unused: u64,
    value: T,
}

/// new is only implemented for T with known size
impl<T> Container<T> {
    fn new(value: T) -> Self {
        Self { _unused: 1, value }
    }
}

fn main() {
    let mut c = Container::new(42_i32);

    let c_dyn: &mut Container<dyn Hello> = &mut c;
    assert_eq!(std::mem::size_of_val(&c_dyn), 16);

    c_dyn.value.hello(); // print "hello: 42"

    let (data_ptr, p2): (usize, usize) = unsafe { std::mem::transmute(c_dyn) };
    assert_eq!(data_ptr, &c as *const _ as usize);

    // data_ptr points to c, but what does p2 point to?
    println!("0x{:x} 0x{:x}", data_ptr, p2); // 0x7ffee8803448 0x55bd42e4d9b8
}

1 Like

Yes, it is a vtable pointer. This is documented.

It does point to vtable for i32 as Hello, but Container<dyn Hello> does not implement Hello, it seems a little wired.

    let p: &dyn Hello = &0_i32;
    let (_, hello_vtable): (usize, usize) = unsafe { std::mem::transmute(p) };

    /// p2 is vtable for i32 as Hello
    assert_eq!(hello_vtable, p2);

What's weird about it?

The vtable pointer isn't for Container, it's for i32, which does implement Hello.

1 Like

In this document, the second field of pointer to trait object should be vtable of methods that Container<T> implements Hello:

Each instance of a pointer to a trait object includes:

- a pointer to an instance of a type T that implements SomeTrait
- a virtual method table, often just called a vtable, which contains, for each 
  method of SomeTrait and its supertraits that T implements, a pointer to T's
  implementation (i.e. a function pointer).

So by this definition, c_dyn is not a pointer to trait object? And obviously, it is neither a pointer to slice, I want to know is there any document about this type of fat pointer?

1 Like

From that same link:

Trait objects are written as the optional keyword dyn followed by a set of trait bounds

Collection<dyn Hello> is not a set of trait bounds optionally preceded by dyn, so as far as this page is concerned, no, Collection<dyn Hello> is not a trait object type. dyn Hello is a trait object; Collection<dyn Hello> is "merely" a struct that contains one.

The most common general term for slices, trait objects and things like Collection that hold them is "dynamically sized types" or DSTs.

1 Like

That doesn't seem like quite the right generalization — or, at least, it doesn't capture all the cases where a fat pointer exists. Box<dyn Foo> is Sized, so it is not a DST, but it has a vtable pointer. (And so does Rc etc. and any user-defined type that contains a DST through some pointer type.)

[Edit:] Never mind, I was slightly confused. Box is the (smart) pointer, and so the vtable pointer lives there. Otherwise, containers with multiple Boxes wouldn't work becuse there would be too many pointers, such as in:

use std::fmt::Debug;
struct Foo<T: ?Sized, U: ?Sized> {
    a: Box<T>,
    b: Box<U>,
}
fn main() {
    let foo = Foo {
        a: Box::new(1) as Box<dyn Debug>,
        b: Box::new(2) as Box<dyn Debug>,
    };
}

My question is not for DST itself, but for pointers to DST, these two type of fat pointers are common:

  • &dyn Trait
  • &[u8]

When I read code in rust-gc, I see something like this NonNull<GcBox<dyn Trace>>. It is a fat pointer but is not of these two types, so I'm curious about what this pointer is.

Anyway, this is not terribly important. I'll mark it as resolved.

NonNull is a pointer type, so when it points to a DST it becomes a fat pointer, just like &dyn Trait, *dyn Trait, or Box<dyn Trait>. This applies equally to all varieties of pointers to DSTs.

1 Like

With a type like this, a reference &Container<T> will contain the same kind of metadata as a reference &T would. I.e. a &Container<[u8]> consists of a pointer to the struct and the length of the contained slice, and a &Contained<dyn Trait> consists of a pointer to the struct and a pointer to the vtable of the contained dyn Trait trait object.

E.g. when you have a variable let reference: &Container<dyn Trait> = …; and create a reference to its field with the expression &reference.value, which is a &dyn Trait, then this &dyn Trait fat pointer is created by adding the offset of the value field to the pointer part of reference and keeping the metadata part (i.e. the vtable pointer) the same.


See also

Pointee in core::ptr - Rust

Pointers to dynamically-sized types are said to be “wide” or “fat”, they have non-zero-sized metadata:

  • For structs whose last field is a DST, metadata is the metadata for the last field
  • For the str type, metadata is the length in bytes as usize
  • For slice types like [T] , metadata is the length in items as usize
  • For trait objects like dyn SomeTrait , metadata is DynMetadata<Self> (e.g. DynMetadata<dyn SomeTrait>)

in particular note the first bullet point about “structs whose last field is a DST”.

3 Likes

This is exactly what I want, thanks!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.