How Can I Unsafely Convert a `Vec<u8>` to an Owned Type?

Essentially I want to do this:

let raw_data: Vec<u8> = ...;
let data: &T = unsafe { &*(raw_data.as_ptr() as *const T) }

But instead of getting a reference, I want to get an owned T.

Edit: Oh, it just hit me. It's on the heap, so I have to get a Box<T>. Is this right? :

let raw_data: Vec<u8> = ...;
let data: Box<T> = unsafe { Box::from_raw(raw_data.into_boxed_slice().as_mut_ptr() as *mut u8 as *mut T) }

Note that for this to be safe, at least the following must be satisfied:

  • All bytes of the Vec must be valid for corresponding bytes of T. This might be impossible to prove, if T is not repr(C), since in this case you won't know an exact layout.
  • The Vec backing storage must be properly aligned for T. This is, AFAIK, impossible to ensure, unless Vec was created from Box<T> at the first place.

Probably there's something more that I forgot.

2 Likes

The easiest way is probably something like this:

let raw_data: Vec<u8> = ...;
let ptr = raw_data.as_ptr() as *const T;
raw_data.set_len(0);
let data: T = std::ptr::read_unaligned(ptr);

Here you avoid any concerns about alignment with read_unaligned. Of course, the memory must still store a valid value of type T.

5 Likes

Just to check - does setting the length of the raw_data to zero decouple the Vec pointer range from the T it held so that when raw_data is dropped it doesn't accidentally take what has become data out with it?

How might this change for using an array of [MaybeUninit<u8>; N]? Would that also require read_unaligned to handle alignment issues? Or is there a case where mem::transmute would work? Would the size of u8/u16/etc. have to be chosen to match the alignment of T in that case?

I mean, in principle setting the length to zero is not even required. In the destructor's eyes, it's just a sequence of bytes, and a sequence of bytes has not destructor. The T returned by read_unaligned is completely detached from the vector — it's a copy of the bytes from the vector.

It would mostly be the same, and would most likely also require read_unaligned.

There are some cases, but I think the std::ptr::read methods are simpler for this case. To use mem::transmute, you would need an [MaybeUninit<u8>; N] with N being exactly the size of T.

Interesting. Does that imply that std::ptr::read methods would generally perform a full copy of the bytes to a new register for the return value? Could the compiler optimize that away, especially in the case of larger forms of T? Although, Vec's data are on the heap, so perhaps the read effort isn't worth worrying about.

Yes, when you use std::ptr::read, this will perform a copy of the bytes behind the pointer. It can optimize this like it can optimize any other copy or move.

Thanks! I'm still just getting started learning unsafe. I'm not sure yet just how much it can mess up compiler optimization.

1 Like

Ah, that makes sense, I forgot about alignment.

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.