Safe way to convert Vec<u8> of len n*4 to a Vec<Rgba> of len n

I have the following struct:


#[repr(C)]
pub struct Rgba {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

I also have v: Vec<u8> of len n*4, guaranteed to be rgba format. Is there a way, without manually looping through every element, to "cast" this Vec<u8> of len n*4 to a Vec<rgba> of len n ?

Given that:

alloc::Layout::<[u8; 4 * N]>::new() == alloc::Layout::<[Rgba; N]>::new()

(because mem::size_of::<Rgba>() == 4 * mem::size_of::<u8>() AND because

mem::align_of::<Rgba>() == mem::align_of::<u8>()

(that last condition is paramount for the safety of such cast).

You can then define the following function:

#![feature(vec_into_raw_parts)]

fn transmute_vec (vec: Vec<u8>) -> Vec<Rgba>
{
    use ::std::{alloc, mem};

    const K: usize =
        mem::size_of::<Rgba>() / mem::size_of::<u8>()
    ;

    assert_eq!(
        mem::align_of::<Rgba>(),
        mem::align_of::<u8>(),
    );

    assert_eq!(vec.len() % K, 0);
    assert_eq!(vec.capacity() % K, 0);
    let (ptr, len, cap): (*mut u8, _, _) = vec.into_raw_parts();
    unsafe {
        // # Safety
        //
        //  - It is safe to transmute between `[u8; K]` and `Rgba`,
        //
        //      - (thanks to `Rgba` being a `#[repr(C)]` struct with `K` u8 fields)
        //
        //   - `Layout::<[u8; K * N]>::new() == Layout::<[Rgba; N]>::new()`
        Vec::from_raw_parts(
            ptr.cast::<Rgba>(),
            len / K,
            cap / K,
        )
    }
}

The above requires nightly dues to the vec_into_raw_parts feature. You can reimplement it in stable with the following code:

trait VecIntoRawParts {
    type Item;

    fn into_raw_parts (self: Self)
      -> (*mut Self::Item, usize, usize)
    ;
}

impl<T> VecIntoRawParts for Vec<T> {
    type Item = T;
    
    fn into_raw_parts (self: Self)
      -> (*mut Self::Item, usize, usize)
    {
        let mut this = ::core::mem::ManuallyDrop::new(self);
        let length = this.len();
        let capacity = this.capacity();
        (this.as_mut_ptr(), length, capacity)
    }
}
3 Likes

This can only be done safely if the capacity is a multiple of 4. Generally speaking that's not something you can guarantee unless you use Vec::with_capacity to create it.

https://doc.rust-lang.org/std/vec/struct.Vec.html

Vec does not guarantee any particular growth strategy when reallocating when full, nor when reserve is called. The current strategy is basic and it may prove desirable to use a non-constant growth factor. Whatever strategy is used will of course guarantee O(1) amortized push .

Because rust's current expansion strategy is to double the capacity on overfill, you will seldom run into this issue, but it is still possible:

fn main() {
    let mut v = vec![1, 2, 3];
    v.push(4);
    println!("{}", v.capacity());  // Currently prints 6
}

My crate slice_of_array will allow you to safely convert slices of the vec: This is to say, any owned data you pass around must be Vec<u8>, but you can easily view it as &[Rgba] or &mut [Rgba] at any time.

use slice_of_array::prelude::*;

unsafe impl slice_of_array::IsSliceomorphic for Rgba {
    type Element = u8;
    const LEN: usize = 4;
}

fn main() {
    let v: Vec<u8> = vec![1, 2, 3, 255, 1, 2, 3, 255];

    let rgba: &[Rgba] = v.nest(); // panics if len not divisible by 4
    assert_eq!(rgba[0], Rgba { r: 1, g: 2, b: 3, a: 255 });
}
6 Likes

This is interesting. CAPACITY (as opposed to length) being multiple of 4 is not something I considered.

  1. I am reading data from a HTML Canvas Element: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData

  2. The ImageData https://developer.mozilla.org/en-US/docs/Web/API/ImageData has a data field, which is an https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray

  3. This is then read into Rust/wasm32/stdweb via https://docs.rs/stdweb/0.4.20/stdweb/web/struct.TypedArray.html

  4. We get a vec via calling ".tovec" -- this definitely has length that is multiple of 4. As for capacity, I'm not sure.

In this case, capacity is equal to length, but only because the implementation of stdweb::TypedArray::to_vec uses Vec::with_capacity.

If you want to remain safe, you can do the following:

let slice: Box<[u8]> = vec.into();
let vec: Vec<u8> = slice.into();

The docs of Vec guarantee that this will set cap = len and will only reallocate if necessary. (oddly enough, there is still no such guarantee given for shrink_to_fit(), so stay away from that for now)

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.