Alexander Vtyurin
Alexander Vtyurin

Reputation: 311

How to typecast fixed size byte array as struct?

I want to reinterpret a stack allocated byte array as a stack allocated (statically guaranteed) struct without doing any work - just to tell the compiler that "Yes, I promise they are the same size and anything". How do I do that?

I tried transmute, but it doesn't compile.

fn from_u8_fixed_size_array<T>(arr: [u8; size_of::<T>()]) -> T {
    unsafe { mem::transmute(arr) }
}
cannot transmute between types of different sizes, or dependently-sized types E0512 
Note: source type: `[u8; _]` (this type does not have a fixed size) 
Note: target type: `T` (this type does not have a fixed size)

There is also this variant of such a function, that compiles, but it requires T to be Copy:

fn from_u8_fixed_size_array(arr: [u8; size_of::<T>()]) -> T {        
    unsafe { *(&arr as *const [u8; size_of::<T>()] as *const T) }
}

Upvotes: 1

Views: 248

Answers (1)

prog-fh
prog-fh

Reputation: 16785

With Rust 1.64 I have a compilation error on [u8; size_of::<T>()] (cannot perform const operation using T). I tried with a const generic parameter but the problem is still the same (I cannot introduce a where clause to constrain this constant to match size_of::<T>()).

Since the array is passed by value and the result is a value, some bytes have to be copied ; this implies a kind of memcpy(). I suggest using a slice instead of an array and checking the size at runtime.

If you are ready to deal with undefined behaviour, you might consider the second version which does not copy anything: it just reinterprets the storage as is. I'm not certain I would do that, however...

Edit

The original code was compiled with nightly and a specific feature. We can simply use transmute_copy() to get the array by value and emit a value.

And, I think the functions themselves should be qualified with unsafe instead of just some of their operations, because nothing guaranties (statically) that these conversions are correct.

#![feature(generic_const_exprs)] // nightly required

unsafe fn from_u8_slice_v1<T>(arr: &[u8]) -> T {
    let mut result = std::mem::MaybeUninit::<T>::uninit();
    let src = &arr[0] as *const u8;
    let dst = result.as_mut_ptr() as *mut u8;
    let count = std::mem::size_of::<T>();
    assert_eq!(count, arr.len());
    std::ptr::copy_nonoverlapping(src, dst, count);
    result.assume_init()
}

unsafe fn from_u8_slice_v2<T>(arr: &[u8]) -> &T {
    let size = std::mem::size_of::<T>();
    let align = std::mem::align_of::<T>();
    assert_eq!(size, arr.len());
    let addr = &arr[0] as *const _ as usize;
    assert_eq!(addr % align, 0);
    &*(addr as *const T) // probably UB
}

unsafe fn from_u8_fixed_size_array<T>(
    arr: [u8; std::mem::size_of::<T>()]
) -> T {
    std::mem::transmute_copy(&arr)
}

fn main() {
    let a = [1, 2];
    println!("{:?}", a);
    let i1 = unsafe { from_u8_slice_v1::<i16>(&a) };
    println!("{:?}", i1);
    let i2 = unsafe { from_u8_slice_v2::<i16>(&a) };
    println!("{:?}", i2);
    let i3 = unsafe { from_u8_fixed_size_array::<i16>(a) };
    println!("{:?}", i3);
}
/*
[1, 2]
513
513
513
*/

Upvotes: 1

Related Questions