Is it OK to create references to unsized types by `transmute`?

I want to make newtype around the slice so I am wondering if such code is valid:

#[derive(Eq, PartialEq, Debug)]
struct UniqueArray<const N: usize>([i32; N]);
#[derive(Eq, PartialEq, Debug)]
struct UniqueVec(Vec<i32>);
#[derive(Eq, PartialEq, Debug)]
#[repr(transparent)]
struct UniqueSlice([i32]);

impl<const N: usize> Deref for UniqueArray<N>{
    type Target = UniqueSlice;
    
    #[inline]
    fn deref(&self)->&UniqueSlice{
        unsafe{
            transmute(self.0.as_slice())
        }
    }
}

impl Deref for UniqueVec{
    type Target = UniqueSlice;
    
    #[inline]
    fn deref(&self)->&UniqueSlice{
        unsafe{
            transmute(self.0.as_slice())
        }
    }
}

I checked that tests seem to work and miri doesn't seem to complain but I wonder if this code valid in semantic sense and would be broken in future Rust release.

References and pointers to different unsized types are not guaranteed to have the same layout, even though this holds on current compiler versions. The preferred method is to use a pointer cast (Rust Playground):

use core::ops::Deref;

#[derive(Eq, PartialEq, Debug)]
struct UniqueArray<const N: usize>([i32; N]);
#[derive(Eq, PartialEq, Debug)]
struct UniqueVec(Vec<i32>);
#[derive(Eq, PartialEq, Debug)]
#[repr(transparent)]
struct UniqueSlice([i32]);

impl<const N: usize> Deref for UniqueArray<N> {
    type Target = UniqueSlice;

    #[inline]
    fn deref(&self) -> &UniqueSlice {
        unsafe { &*(self.0.as_slice() as *const [i32] as *const UniqueSlice) }
    }
}

impl Deref for UniqueVec {
    type Target = UniqueSlice;

    #[inline]
    fn deref(&self) -> &UniqueSlice {
        unsafe { &*(self.0.as_slice() as *const [i32] as *const UniqueSlice) }
    }
}
7 Likes

In addition to using pointer casting instead, I think you should probably also have #[repr(transparent)] applied to the UniqueSlice type.

1 Like

The bstr crate uses

#[repr(transparent)]
struct BStr([u8]);

and transmutes from &[u8] to &BStr (here, for example). That's not an argument that this is a rigorously justified thing to do—my sense from reading the Unsafe Code Guidelines is that it's not yet officially guaranteed to work—but perhaps indicates that it's unlikely to break in practice.

Using unstable features, I think you could also do

fn cast_to_unique(slice: &[i32]) -> &UniqueSlice {
    let ptr: *const () = slice.as_ptr().cast();
    let len = slice.len();
    unsafe { &*std::ptr::from_raw_parts(ptr, len) }
}

I believe repr(transparent) is still necessary for that to be correct. See here and the documentation for the Pointee trait.

You don't need to disassemble and re-assamble the pointer. Pointer casts work directly from unsized type to unsized type as long as the metadata matches.


(Edit: as @LegionMammal978 already demonstrated...)

2 Likes