rust

How to allocate a DST struct


I am trying to allocate a DST and I started noticing a problem. The allocate_zeroed function returns a [u8], which is a fat pointer with the size of the whole struct. I am casting it to a fat pointer *mut DST, which reinterprets the fat pointer size to be the data size. This is incorrect; I want the fat pointer size to represent the data size, not the size of the entire DST in bytes.

use std::alloc::{Allocator, Global, Layout};

#[repr(C)]
struct DST {
    other_data: usize,
    other_data2: usize,
    other_data3: usize,
    other_data4: usize,
    data: [u8],
}


unsafe fn create(data_size: usize) {
    let fat_ptr = Global
        .allocate_zeroed(Layout::from_size_align((size_of::<usize>() * 4) + data_size, 8).unwrap())
        .unwrap()
        .as_ptr() as *mut DST;
    assert_eq!((*fat_ptr).data.len(), data_size + size_of::<usize>() * 4); // Pass
    assert_eq!((*fat_ptr).data.len(), data_size); // Failed
}

I want to change the fat pointer size (if that's possible) to data_size. Or make the the fat pointer have the correct size.


Solution

  • The behaviour you are witnessing is documented in The Rust Reference section on type cast expressions, under Pointer-to-pointer cast (emphasis added):

    *const T / *mut T can be cast to *const U / *mut U with the following behavior:

    [ deletia ]

    • If T and U are both unsized, the pointer is also returned unchanged. In particular, the metadata is preserved exactly.

      For instance, a cast from *const [T] to *const [U] preserves the number of elements. Note that, as a consequence, such casts do not necessarily preserve the size of the pointer’s referent (e.g., casting *const [u16] to *const [u8] will result in a raw pointer which refers to an object of half the size of the original). The same holds for str and any compound type whose unsized tail is a slice type, such as struct Foo(i32, [u8]) or (u64, Foo).

    Instead you must first manually create a pointer with the correct metadata for your struct.

    On stable Rust, this can be done using e.g. std::ptr::slice_from_raw_parts_mut while on nightly Rust with the ptr_metadata feature you can go more directly via std::ptr::NonNull::from_raw_parts_mut.

    Note also that your layout calculations are incorrect, as you are ignoring padding (both between the fields and of the resulting struct).

    Playground links to working versions: stable and nightly.