Pointer metadata on stable

Hey, look! It seems to work! It's mostly safe, except the hacky part. It passes miri. Playground.

How fragile is it to transmute *const [()] to a dynamically-sized type where the pointer isn't actually pointing to the slice?

#[repr(C)]
pub struct FatPointer<T: Sized> {
    /// a pointer to the `FatPointer` will be a valid pointer to T
    item: T,
    /// and this makes it dynmically-sized, while having 0 size in the struct.
    dst_hack: [()],
}

impl<T: Sized> FatPointer<T> {
    pub fn from_ref_with_metadata(item: &T, metadata: usize) -> &Self {
        unsafe {
            // A slice of zero-sized type always occupies zero size itself,
            // so the slice is not claiming to occupy any invalid memory.
            // The isize::MAX limit applies only to non-ZST allocations, so every usize value is fine.
            // The pointer is real, non-null, and aligned.
            let dynamically_sized = std::ptr::slice_from_raw_parts(item as *const T as *const (), metadata);
            &*(dynamically_sized as *const Self)
        }
    }
}

impl<T: Sized> FatPointer<T> {
    pub fn metadata(&self) -> usize {
        self.dst_hack.len()
    }
}

impl<T: Sized> std::ops::Deref for FatPointer<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.item
    }
}

pub struct Foo {
    pub greet: String,
}

impl Foo {
    pub fn with(&self, val: usize) -> &FatPointer<Self> {
        FatPointer::from_ref_with_metadata(self, val)
    }
}

fn main() {
    let foo = Foo { greet: "hello".into() };
    let foo_ref1 = foo.with(1234);
    let foo_ref2 = foo.with(23456);
    assert_eq!(2 * std::mem::size_of::<usize>(), std::mem::size_of_val(&foo_ref1));
    assert_eq!(2 * std::mem::size_of::<usize>(), std::mem::size_of::<&FatPointer<Foo>>());
    assert_eq!("hello", foo_ref1.greet);
    assert_eq!(1234, foo_ref1.metadata());
    assert_eq!(23456, foo_ref2.metadata());
    assert_eq!("hello", foo.greet);
}
3 Likes

I’d probably use a pointer cast instead of transmute

&*(dynamically_sized as *const Self)

otherwise one could argue that the layout of fat pointers isn’t stable (AFAIR).

1 Like

I looked into this for my RustConf talk and was unable to find any evidence that the layout (or even size) is guaranteed.

Part of the reason why those from_raw_parts APIs exist is because we have not committed to any layout.