What Does My Type Look Like?

Let's say I have a type, it looks a bit like this:

struct MyMapping;
impl Mapping for MyMapping {
    fn data_type() -> &'static str { "type" }

And now let's say I have another type that takes this as a generic parameter:

MyType<M: Mapping> {
    field: String,
    phantom: PhantomData<M>

Putting them together I might have:

let mytype = MyType::<MyMapping>::default();

What I'd like to know is what does mytype look like at runtime? Does it lumber around with an instance of MyMapping? Or is there something else going on because there are no fields or non-static methods on MyMapping?

Basically I'd like to know what kind of memory overhead I'm introducing by carrying around the generic parameter.

Sorry if I can't articulate my question very well :slight_smile:

Generic parameters have no runtime overhead in and of themselves. An instance of MyType<anything> contains a String, and nothing else; since it doesn't contain a field of type M, the fields of MyMapping are immaterial.

Even if you did have a field of type M in MyType, it would still not add any bytes because MyMapping is a zero-sized type. If you added a field of type usize to MyMapping and added a field of type M or MyMapping to MyType, then an instance of MyType would have the usize field.

As @sorear wrote, there is no runtime overhead. The PhantomData<M> is only relevant at compile time. To confirm, you can check the runtime size of a type like this:

println!("MyMapping: {}", mem::size_of::<MyMapping>());
println!("String: {}", mem::size_of::<String>());
println!("MyType<MyMapping>: {}", mem::size_of::<MyType<MyMapping>>());
MyMapping: 0
String: 24
MyType<MyMapping>: 24

Rust Playground link

1 Like

The Rustonomicon discusses zero-sized types and PhantomData.

Thanks! I was hoping that was the case. I might have to have a poke through mem, I thought it was just for unsafe stuff so have avoided it so far.

mem contains the safe functions swap and replace that are oh so useful.

Note that even through the generic parameters, phantom fields and fields of Zero-Sized-Types do not introduce runtime overhead you cannot know how your type looks like at runtime as long as it is not repr(C).

This is because the memory layout of rust types is not specified, yes it is unlikely that the layout is any different than that of String in your case but the compiler is free to insert any kind of padding if it want to.
(And change it's behaviour in this regret with any version, because the rust-ABI is, I think, not stabilized.)

So if you want to do any assumptions about the layout of a struct a runtime it is necessary to use #[repr(C)] struct ... witch will then use a C-compatible memory layout.