1

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) }
}
2
  • Which version of compiler do you use? I cannot obtain the same error on the first example, and the second one fails also on [u8; size_of::<T>()] (cannot perform const operation using T). Commented Oct 14, 2022 at 16:12
  • 1.66 nightly. Don't forget about #![feature(generic_const_exprs)] Commented Oct 14, 2022 at 16:15

1 Answer 1

1

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
*/
Sign up to request clarification or add additional context in comments.

7 Comments

Looks like I'm missing something about rust. 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() We're on the stack right? Why there is no way for a compiler to simply pop a value from the stack and then push it back, but treating it as a different type?
By the way, this constraint is where [u8; size_of::<T>()]: Sized.
Adding to my first comment: and why there is no way to tell the compiler please, don't do anything with this data, just know that from now on I want to treat it as this type; I promise to you, that sizes are the same and this won't fail in any case of my program. This is what typecasting is, isn't it?
@AlexanderVtyurin "Why there is no way for a compiler to simply pop a value from the stack and then push it back" - popping and pushing values is kind of the definition of copying memory. Also note that what we are talking about a function call here, and not all architectures use the stack to pass values to/from a function. In fact, most don't. x86/x64 for example uses registers for the first couple of parameters and only uses the stack afterwards. I think wasm is one of the few targets that actually is fully stack based.
@AlexanderVtyurin Even when it as actually stack-based, how could we be certain that the array we consume in the function was at the top of the stack and can be popped in order to push something else at the exact same place?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.