Usize as &[u8], Vec<f32> as &[u8]

I'm looking to use the write function from

This only takes &[u8] as an arg.

what is the simpliest way to 'view' a usize / Vec<f32> as a &[u8] ?

You can use usize::to_ne_bytes to get the native-endian bytes of a usize. (There are also variants that convert to big- or little-endian.)

For the Vec<f32> you can use a function like this:

fn as_bytes(v: &[f32]) -> &[u8] {
    unsafe {
            v.as_ptr() as *const u8,
            v.len() * std::mem::size_of::<f32>())

If I may ask for one more favor -- how would you convert a &[u8] or Vec<u8> of len n * std::mem::size_of::<f32>() to a Vec<f32> ?

If I could suffer the overhead of a copy, then I would probably use byteorder, which exposes safe APIs to do your conversion in both directions. See ByteOrder::read_f32_into for &[u8] -> &[f32] and ByteOrder::write_f32_into for &[f32] -> &[u8]. This will also handle endianness for you, if that's a concern. (If it's not, you can use native endian.)

If you need a zero cost way of going from a &[u8] to a &[f32], then it's pretty much the same as what @mbrubeck provided, but in reverse. However, you also need to account for alignment since &[f32] has a higher alignment requirement than &[u8]. So something like this:

fn as_bytes(v: &[u8]) -> &[f32] {
    assert_eq!(v.len() % 4, 0);
    assert_eq!(v.as_ptr() as usize % std::mem::align_of::<f32>(), 0);
    unsafe {
            v.as_ptr() as *const f32,
            v.len() / 4,

Now if you need to do Vec<u8> <-> Vec<f32> then that is trickier. e.g., If you start with a Vec<u8> and convert that to a Vec<f32> (assuming length and alignment are correct, as above) but then allowed that Vec<f32> to be deallocated, then you might wind up with UB because you'll be deallocating an allocation that was made with a different alignment. The same would hold in the reverse direction. It is much safer to do Vec<u8> -> &[f32] or Vec<f32> -> &[u8].


ByteOrder is really nice. The data is coming from / going to disk. IO will probably dwarf the in memory copy.

You may also want to look at [T]::align_to. It has the following signature:

pub unsafe fn align_to<U>(&self) -> (&[T], &[U], &[T]);

It takes your data of T and partitions it into "pre-U", "valid U", and "post-U":

Index:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
Data:   ?? ?? 24 03 FF EE AA 11 22 33 44 55 66 77 ?? ??
Result:       ├───┤ ├─────────────────────┤ ├───┤
Type:         &[u8]         &[f32]          &[u8]

So this code:

let my_data: &[u8] = &[
  0x00, 0x00, //These are padding bytes as ??s
  0x24, 0x03, 0xFF, 0xEE, 0xAA, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
  0x00, 0x00, //??
let (l, m, r) = unsafe {my_data[2..=0xD].align_to::<f32>()};
assert_eq!(l, [0x24, 0x03]);
assert_eq!(m, [
    f32::from_ne_bytes([0xFF, 0xEE, 0xAA, 0x11]), 
    f32::from_ne_bytes([0x22, 0x33, 0x44, 0x55])
assert_eq!(r, [0x66, 0x77]);
1 Like

So the middle is the longest chunk where start = multiple of alignment, end+1 = multiple of alignment, and the front/end are the remaining parts?

Yes... it aligns data to the alignment of the other type U. l is misaligned to be an f32 since it needs to be aligned to four bytes (Coincidentally I know this, don't leverage previous knowledge like this in a production scenario and always use std::mem::align_of::<T>()). m is properly aligned and can be read as f32, and finally r is what's left over since it's too short to be another f32.