Is this a sound use of `align_to_mut`?

I want to render Conway's game of life into a u8 buffer. Each group of 4 u8 represents a pixel with one u8 for each channel (rgba).

Here is the code:

enum Cell {
    Alive,
    Dead,
}

pub fn render_into(cells: &[Cell], buffer: &mut [u8]) {
    static BLACK: u32 = 0xFF_00_00_00;
    static WHITE: u32 = 0xFF_FF_FF_FF;
    let (_, pixels, _) = unsafe { buffer.align_to_mut::<u32>() };
    for (cell, pixel) in cells.iter().zip(pixels.iter_mut()) {
        *pixel = match cell {
            Cell::Alive => BLACK,
            Cell::Dead => WHITE,
        };
    }
}

Is this sound?


To answer the obvious question, I can't use a u32 buffer because this is a wasm module and actually the buffer is a view into a JavaScript ImageData object.

This looks sound but is widely unecessary and only incidentally correct on your own platform, due to endianess of integers. Also, consider that part of the buffers will not have been written to and the length of the slice pixels might be different than simple buffer.len() / 4. You can instead achieve similar effect in safe code by iterating over chunks of 4 bytes and converting those slices to proper arrays (that is [u8; 4]). You can add manual alignment if not writing to part of the buffer was actually intended.

use std::convert::TryInto;

pub fn render_into(cells: &[Cell], buffer: &mut [u8]) {
    static BLACK: [u8; 4] = [0, 0, 0, 0xff];
    static WHITE: [u8; 4] = [0xff; 4];
    for (cell, pixel) in cells.iter().zip(buffer.chunks_exact_mut(4)) {
         let pixel: &mut [u8; 4] = pixel.try_into().unwrap();
        *pixel = match cell {
            Cell::Alive => BLACK,
            Cell::Dead => WHITE,
        };
    }
}
2 Likes

I tried this and it seems to be on par with the unsafe version! I was also worried about the endianess. Thanks!

(Aside: this is a place where I'd use const rather than static, since the usage is by value)

What is the difference between const and static?

You can think of the compiler as copy-pasting the definition for a const at every location it is used. With a static, there will only ever be one instance of that variable in your program and every use of the static will refer to the same piece of memory.

I generally use const for magic numbers and things which are "constants". static variables are for when you need a singleton or global variable for whatever reason.

You may also want to read the relevant pages in the Rust reference (const, static).

7 Likes

While this is sound (because u32 allows all bit patterns and u8 has no padding) it's not what you want. Remember this note in the docs:

The method may make the middle slice the greatest length possible for a given type and input slice, but only your algorithm's performance should depend on that, not its correctness. It is permissible for all of the input data to be returned as the prefix or suffix slice.

So code that ignores the prefix and suffix can't be correct.

3 Likes