Memory Layout and Representation

Pointer Metadata

Pointer metadata is the auxiliary information attached to a pointer or reference that enables the compiler and runtime to determine the size, alignment, and dynamic dispatch table of the pointee. Every pointer type *const T in Rust logically consists of a data address (*const ()) and a metadata component (<T as Pointee>::Metadata).

Metadata Categories by Pointee Type

Pointee Type T Metadata Type Metadata Size (64-bit) Meaning of Metadata
Any T: Sized () 0 bytes Size and alignment are fixed at compile time
[U] or str usize 8 bytes Number of elements ([U]) or UTF-8 bytes (str)
dyn Trait DynMetadata<dyn Trait> 8 bytes Non-null pointer to the static vtable for the concrete type
Custom DST Struct<..., [U]> usize 8 bytes Element count of the unsized tail slice
Custom DST Struct<..., dyn Trait> DynMetadata<dyn Trait> 8 bytes Vtable pointer of the unsized tail field

Extracting and Constructing Slice Metadata

For slices (&[T], *const [T]), the metadata is the element count len: usize (not the byte length when size_of::<T>() > 1). Standard library functions decompose and reconstruct slice pointers from their data pointer and length metadata:

  • slice.as_ptr() and slice.len() extract the thin data pointer (*const T) and length metadata (usize).
  • std::ptr::slice_from_raw_parts(data, len) combines a thin *const T and usize length into a fat raw pointer *const [T] without dereferencing or asserting validity.
  • std::slice::from_raw_parts(data, len) (unsafe) constructs a shared slice reference &[T].
use std::ptr;

fn main() {
    let elements: [u32; 4] = [100, 200, 300, 400];
    let slice: &[u32] = &elements[1..4];

    let data_ptr: *const u32 = slice.as_ptr();
    let metadata_len: usize = slice.len();

    let raw_fat_ptr: *const [u32] = ptr::slice_from_raw_parts(data_ptr, metadata_len);

    println!("data_ptr:             {:p}", data_ptr);
    println!("metadata (len):       {} elements", metadata_len);
    println!("size_of_val(&slice):  {} bytes", unsafe { size_of_val(&*raw_fat_ptr) });
    println!("reconstructed slice:  {:?}", unsafe { &*raw_fat_ptr });
}
data_ptr:             0x16fdfee64
metadata (len):       3 elements
size_of_val(&slice):  12 bytes
reconstructed slice:  [200, 300, 400]

Casting a Fat Pointer to a Thin Pointer

Casting a fat raw pointer (*const [T], *const str, or *const dyn Trait) to a thin pointer (*const () or *const u8) with as discards the metadata word and retains only the data address.

trait Greeter {
    fn greet(&self) -> &'static str;
}

struct Bot {
    id: u64,
}

impl Greeter for Bot {
    fn greet(&self) -> &'static str {
        "hello"
    }
}

fn main() {
    let bot = Bot { id: 7 };
    let thin_ptr: *const Bot = &bot;
    let fat_ptr: *const dyn Greeter = &bot;
    let stripped_ptr: *const () = fat_ptr as *const ();

    println!("size of thin_ptr:     {} bytes", size_of_val(&thin_ptr));
    println!("size of fat_ptr:      {} bytes", size_of_val(&fat_ptr));
    println!("size of stripped_ptr: {} bytes", size_of_val(&stripped_ptr));
    println!(
        "same data address:    {}",
        (thin_ptr as *const ()) == stripped_ptr
    );
    println!("bot.id = {}, greet = {}", bot.id, bot.greet());
}
size of thin_ptr:     8 bytes
size of fat_ptr:      16 bytes
size of stripped_ptr: 8 bytes
same data address:    true
bot.id = 7, greet = hello

Fat Pointer Equality Pitfalls (std::ptr::eq)

std::ptr::eq(a, b) compares both the data address and the pointer metadata when given fat pointers (*const [T] or *const dyn Trait). For trait objects, two copies of the same function or trait implementation across different codegen units can have distinct vtable addresses even when pointing to the exact same object. Comparing thin data addresses directly uses std::ptr::addr_eq(a, b).

use std::ptr;

fn main() {
    let array = [1_u8, 2, 3, 4];
    let slice_two: &[u8] = &array[0..2];
    let slice_four: &[u8] = &array[0..4];

    // Both slices start at `&array[0]`, but have different length metadata (2 vs 4).
    println!("ptr::eq(slice_two, slice_four):      {}", ptr::eq(slice_two, slice_four));
    println!("ptr::addr_eq(slice_two, slice_four): {}", ptr::addr_eq(slice_two, slice_four));
}
ptr::eq(slice_two, slice_four):      false
ptr::addr_eq(slice_two, slice_four): true