Can element size change in Vec::from_raw_parts?

Vec::from_raw_parts says:

T needs to have the same size and alignment as what ptr was allocated with.

can it be interpreted as same byte size?

Can I convert Vec<[u8; 2]> to Vec<u8> with appropriately doubled length and capacity? Alignment stays the same, and len * sizeof stays the same.

This would be allowed with slice::from_raw_parts, as that one is more explicit about its interaction with reinterpreting memory.

I think the correct interpretation is that the Vec::from_raw_parts documentation itself does not allow this. However, given the guarantees provided for Vec's structure/layout, I think this is effectively guaranteed to work.

The tricky part, ofc, is that while Vec<[u8; 2]> -> Vec<u8> is rather simple (half element size, double capacity and length), the reverse direction is a lot harder, as you have to ensure that the capacity evenly divides into the new type.

(And just a reminder for readers: alignment must stay the same when owning an allocation, and may not be changed in any way.)

3 Likes

Basically, if we name:

type T;
let (ptr, orig_len, orig_cap) = Vec::<T>::into_raw_parts(...);

type U;
let (new_len, new_cap) = ...;

then, the operation Vec::<U>::from_raw_parts(ptr.cast(), new_len, new_cap) is sound if, and only if:

  • Layout::<[T; orig_cap]>::new() is the same as Layout::<[U; new_cap]>();
    or, equivalently, that the following two properties hold:

    • T and U have the same alignment,

    • mem::size_of::<T>() * orig_cap = mem::size_of::<U>() * new_cap

  • it is safe to transmute from a &'lt mut [T; orig_len] to a &'lt mut [U; new_len]

    • this implies, for instance, that
      mem::size_of::<T>() * orig_len must be
      ≥ mem::size_of::<U>() * new_len

    • Nitpick

      The above is not completely accurate; while that is sufficient (for the from_raw_parts) to be sound,
      the necessary looser bound for this second property is that:

      it is safe to transmute:

      • from &'lt mut ReprC<[T; orig_len], [MU<T>; orig_cap - orig_len]>
      • to &'lt mut [U; new_len]

      Where:

      • use ::core::mem::MaybeUninit as MU; and

      • #[repr(C)] struct ReprC<_0, _1>(_0, _1);

    • (An interesting thing here, is that this second property is always trivially met when new_len = 0...)

Since T = [u8; 2], U = u8 ≅ [u8; 1] and new_{len,cap} = 2 * orig_{len,cap} satisfy these two properties, the operation is sound.

5 Likes

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.