I want to have a byte slice that is statically guaranteed to have the same size as a type. So I came up with basically this:
/// Bytes of the same size as `T`.
#[repr(packed)]
pub struct Bytes<T: ?Sized> {
buf: ManuallyDrop<T>,
}
impl<T: ?Sized> Bytes<T> {
/// This *is* unsafe!
///
/// Why? Because padding bytes might have undefined values.
pub unsafe fn from_ref(value_ref: &T) -> &Bytes<T> {
&*Self::from_ptr(value_ref)
}
pub fn from_ptr(value_ptr: *const T) -> *const Bytes<T> {
unsafe { mem::transmute(value_ptr) }
}
}
impl<T> Bytes<T> {
pub fn from_slice(buf: &[u8]) -> &Bytes<T> {
assert_eq!(buf.len(), mem::size_of::<T>());
unsafe {
&*Self::from_ptr(buf.as_ptr() as *const T)
}
}
}
impl<T> AsRef<[u8]> for Bytes<T> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Self as *const u8,
mem::size_of::<T>())
}
}
}
MIRI appears to be ok with this, allowing access to every byte in the slice if Bytes
is created from a slice, and panicking as expected if (unsafely) created from a reference and padding bytes are accessed.
My question is if this construction is risking UB by creating an invalid reference to an invalid value? (e.g. Bytes<bool>
)
I'm careful to never actually access this reference except a byte slice, and the only safe way to create a Bytes
reference is to create it from a slice, which appears to avoid the notorious access to undefined values source of UB seen in mem::uninitialized()
(note how I haven't provided any way of creating an actual Bytes
value). Finally, because of #[repr(packed)]
the alignment doesn't matter.