How can I view the memory layout of a type?

If I have some complex type:

Foo {
    bar: Baz(
        Quux(
            Zom(1,
                Nef(
                    Quibble(5)
                )
            )
        )
    )
}

I can output the debugging string derivation and see something like the above. I can also check the size_of it.

But I'm curious if there's a way to see the actual linear layout of the bytes and where each value is within them.

Also, if I access a deeply nested member, is the access constant time based on offsets known at compilation time?

I'm relatively sure that this compiles to an equivalent of (1, 5) where these names are typenames and not functions of any kind. The thing is, that things like these get "compressed" from what is seen in code. What else would there be to store, other than (1, 5)? Rust has no runtime type info built into data structures' representations in memory, so realistically Foo(x, y) is just (x, y).


Note that adding a #[repr(transparent)] ensures that types collapse into each other like that, without dummy padding or anything, while usually useful for ffi or transmuting.

1 Like

I found basically what I was looking for:

https://stackoverflow.com/a/381368

https://github.com/rust-lang/rfcs/issues/1230

For the inline representation (i.e., no dereference), you can view your element as a slice of bytes (Playground).

This outputs (version 1.35.0, Linux x86_64):

Foo {
    bar: Baz(
        Quux(
            Zom(
                1,
                Nef(
                    Quibble(
                        5,
                    ),
                ),
            ),
        ),
    ),
} = [
    0x5,
    0x0,
    0x0,
    0x0,
    0x1,
    0x7f,
    0x0,
    0x0,
]
  1. The first four bytes are those of the i32, in little-endian representation (in practice it would be native endianness; it just so happens that the Playground's platform is natively little endian);

  2. The following byte is the u8;

  3. And the last three bytes are padding bytes (whose contents are always undefined, IIRC).

I am unsure whether those padding bytes will always be there, since I suspect they exist so that the size of a NewType(i32, u8), remains a multiple of the alignment, given that types cannot currently have a stride greater than their size.

  • (In a slice, for all but the first element to be properly aligned, each "previous element", (i.e., every element except the last one) may have extra padding).

  • See the Unsafe Code Guidelines Reference.

2 Likes

The layout isn't guaranteed across compilations. If your accessing a nested field directly there is no additional overhead. (Compiler could even optimise further; instead of having to use an offset calculation, have a more direct result; down to aliasing knowledge.)

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.