... T and U are not zero sized and have same size and alignment.
Hello all,
I know this comes up every once in a while, but I was wondering if there is a safe way to transform / transmute a Vec to a Vec where T and U have the same size and alignment?
Generated assembly doesn't call heap allocators(in fact it doesn't contains any "call" instruction) so it's in-place conversion. And you can see the conversion is vectorized.
To answer the original question, the safest way to transform two vectors is indeed
v.into_iter().map(f).collect()
but it is not guaranteed to be a no-op even when possible. If you really want to reuse the same allocation, you should use pointer casts:
/// Transmutes `Vec<T>` into `Vec<S>` in-place, without reallocation. The resulting
/// vector has the same length and capacity.
///
/// SAFETY: the types `T` and `S` must be transmute-compatible (same layout, and every
/// representation of `T` must be a valid representation of some value in `S`).
pub unsafe fn transform<T, S>(mut v: Vec<T>) -> Vec<S> {
let len = v.len();
let capacity = v.capacity();
let ptr = v.as_mut_ptr().cast::<S>();
// We must forget the original vector, otherwise it would deallocate the buffer on drop.
mem::forget(v);
// This is safe, because we are reusing a valid allocation of the same byte size.
// The first `len` elements of `S` in this allocation must be initialized, which is
// true since `size_of::<T>() == size_of::<S>()`, the first `len` elements of `T` are
// initialized due the safety invariants of `Vec<T>`, and `T` and `S` being
// transmute-compatible by the safety assumptions of this function.
Vec::from_raw_parts(ptr, len, capacity)
}
Note that you can never directly mem::transmute between Vec<T> and Vec<S>, regardless of what T and S are. This is because there are no layout guarantees on #[repr(Rust)] generic types. Even if T and S are transmute-compatible, the fields of the vectors may be layed out in a different order.
Also remember that it's not enough for T and S to have the same layout (size and alignment). u8 and bool have the same layout, but transmuting u8 to bool is invalid (and thus you also can't transmute Vec<u8> to Vec<bool>). This is because bool has only two vaild values, 0 (false) and 1 (true), and having a bool with any other memory representation is UB.
The specialization is used to remove the re-allocation and make the map happen in-place. That part is quite reliable. However, turning it entirely into a no-op will then rely on LLVM optimizations to get rid of the whole loop that executes the mapped operation in-place, and that optimization – since it’s an automatic optimization by LLVM – is generally somewhat less reliable of a thing to happen. Of course LLVM will not deliberately make things worse in the future, but there’s always the possibility for unintentional pessimizations of certain optimizations in corner cases.