0

I want to convert a [u32; 4] to a [u8; 16] in place.

I know it is possible to use bitwise operation to get the 16 u8 from the 4 u32. But I want to convert the array in place. How to do this using safe Rust or unsafe Rust?

let buffer: [u32; 4] = [1, 2, 3, 4];
let hash: [u8; 16]; // how to convert from buffer to hash?

background information: I am calculating a 128-bit hash value, where the [u32; 4] is a buffer holding 4 u32. And I want to convert the array to a [u8; 16], as the final hash.

liginity
  • 311
  • 3
  • 7
  • This has been asked many times before. I think the best solution is using the `align_to()` method of slices, which will give you a slice rather than an array. I'll look up the duplicates. – Sven Marnach Jun 15 '22 at 12:22
  • @SvenMarnach Why is `align_to()` better than `transmute()` in this case? – Chayim Friedman Jun 15 '22 at 12:25
  • @ChayimFriedman Because you at least can't get the alignment wrong, which is possible with `transmute()`. I know that `u8` has an alignment of 1, but in general it's preferable to use solutions where fewer things can go wrong. Of course the downside is that you don't get an array but a slice. – Sven Marnach Jun 15 '22 at 12:34
  • @SvenMarnach Given that `transmute()` is sound, and `align_to()` complicates the code considerably I feel like with a proper safety comment this is fine. – Chayim Friedman Jun 15 '22 at 12:35
  • 1
    Fair enough. This always depends on context, e.g. who is working on this codebase? May the code be used in different context in the future? Will it likely be altered to work on different types? I'm generally much more reluctant to _recommend_ `transmute()` on StackOverflow than to _use_ it myself, because people will use the same answers in different contexts, where it may not be appropriate. – Sven Marnach Jun 15 '22 at 12:40
  • Related questions (possibly duplicates): [Convert array (or vector) of u16 (or u32, u64) to array of u8](https://stackoverflow.com/questions/66631010/convert-array-or-vector-of-u16-or-u32-u64-to-array-of-u8) • [Converting a Vec to Vec in-place and with minimal overhead](https://stackoverflow.com/questions/49690459/converting-a-vecu32-to-vecu8-in-place-and-with-minimal-overhead) • [Converting large number stored in array of u32 to bytes and back](https://stackoverflow.com/questions/48158788/converting-large-number-stored-in-array-of-u32-to-bytes-and-back) – Sven Marnach Jun 15 '22 at 12:52
  • [Similar situation](https://stackoverflow.com/questions/70697768/transmute-struct-into-array-in-rust/70699596#70699596=). Wanting to do something in place with data that fits into a register probably isn't very meaningful. – Caesar Jun 15 '22 at 13:01

3 Answers3

6

You may be tempted to do a transmute, but I would advise against it, mainly because of the endianness, that is the final output would depend on wether your machine is big-endian or little-endian:

pub fn convert(data: &[u32; 4]) -> [u8; 16] {
    unsafe { std::mem::transmute(*data) }
}

Instead I'd just do it the boring way (playground):

pub fn convert(data: &[u32; 4]) -> [u8; 16] {
    let mut res = [0; 16];
    for i in 0..4 {
        res[4*i..][..4].copy_from_slice(&data[i].to_le_bytes());
    }
    res
}

Change to_le_bytes() to to_be_bytes() if you need big-endian instead. Or use to_ne_bytes() to get native endianness if you don't care about that.

If you are worried about performance (but why should you be?) the generated code for x86_64 using native endianness is as good as it gets:

playground::convert:
    movq    %rdi, %rax
    movups  (%rsi), %xmm0
    movups  %xmm0, (%rdi)
    retq
rodrigo
  • 94,151
  • 12
  • 143
  • 190
0

Using unsafe, it is possible easily with transmute():

pub fn convert(v: [u32; 4]) -> [u8; 16] {
    unsafe { std::mem::transmute(v) }
}

I don't think there is an efficient safe way without external crates. If you can adopt extern crates, you can use bytemuck::cast():

pub fn convert(v: [u32; 4]) -> [u8; 16] {
    bytemuck::cast(v)
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
0

Slighly simpler/more idiomatic take on the currently accepted answer:

pub fn convert(source: &[u32; 4]) -> [u8; 16] {
    let mut dest = [0; 16];
    for (dest_c, source_e) in dest.chunks_exact_mut(4).zip(source.iter()) {
        dest_c.copy_from_slice(&source_e.to_le_bytes())
    }
    dest
}

Based on the disassemly, it seems to generate simpler ASM, probably because of stronger assumptions due to iterations entirely based on iterators (I've checked on a array with 64k u32 entries, but I haven't analyzed the ASM thoroughly).

Marcus
  • 5,104
  • 2
  • 28
  • 24
  • This is not "in place" as requested in the question. The original source is borrowed, and the result is a new value with separate storage. – davenpcj Oct 29 '22 at 16:02
  • As I wrote, this is a slighly more iditiomatic take of the (part 2 of the) accepted answer. If you think this should be integrated in the current answer (as update), it makes sense - feel free it suggest it, and I'll delete this after it's integrated. – Marcus Oct 30 '22 at 21:32