Least unsafe way to transmute Vec<T> to Vec<[T; N]>

I have a vector of length L where L is cleanly divisible by N. I would like to convert it in-place (i.e. without copying) into a vector of length L/N of arrays of length N. Is this possible to do soundly if (and I realize this is a big if) the alignment checks out?

The main challenge here is the capacity. It's not enough for the length to be a multiple of N, the capacity must also be a multiple of N. However, if those are both the cases, then it can be done:

fn to_arr<const N: usize, T>(mut vec: Vec<T>) -> Vec<[T; N]> {
    assert!(vec.len() % N == 0);
    assert!(vec.capacity() % N == 0);
    let len = vec.len() / N;
    let cap = vec.capacity() / N;
    let ptr = vec.as_mut_ptr();
    std::mem::forget(vec);
    unsafe {
        Vec::from_raw_parts(ptr.cast(), len, cap)
    }
}

You may find it more reliable to convert boxes instead, as there is no capacity to worry about:

fn to_arr_box<const N: usize, T>(b: Box<[T]>) -> Box<[[T; N]]> {
    assert!(b.len() % N == 0);
    let len = b.len() / N;
    let ptr = Box::into_raw(b).cast::<[T; N]>();
    unsafe {
        Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len))
    }
}

You can convert boxes to and from vectors, so it can also be used to convert vectors.

12 Likes

I think it’s generally preferable to do this as

let mut vec = ManuallyDrop::new(vec);
let ptr = vec.as_mut_ptr();

to avoid any potential issues that moving the vec into the mem::forget() call could cause. Miri doesn’t seem to mind, but better safe than sorry, and using ManuallyDrop is also what the standard library's (unstable) into_raw_parts uses.

6 Likes

On nightly you can use array_chunks().collect(). It'll either keep the allocation or call realloc if it's not a multiple. Adding the appropriate assert will show that it doesn't realloc if capacity fits.

5 Likes

https://docs.rs/bytemuck/latest/bytemuck/allocation/fn.cast_vec.html is the pre-packaged way to do this, if you're okay with pulling in a crate.

Of course for T to [T; N] casts those bounds are a little over restrictive, so maybe the crate should add a version just for the array case some day.

1 Like

That would be nice to have. It could be even more general as "anything that is laid out like a [T; N]", to support cases like #[repr(C)] struct Vec2<T> { x: T, y: T }, as well as T itself.