How to use a struct as a u8 buffer?

Hello Rustians !

I'm really new to rust (24h at max :D).

I'm trying to figure out this problem (at least I would have done in this way in C).

Suppose that I have the following struct

#[repr(C, packed)]
struct MyStruct {
    fieldA : u8,
    fieldB: u16,
    fieldC: u8,
   fieldB: u32
}

Now also suppose that I'm receiving this struct through a UDP Unix Domain Socket.
Rust's recv_from function has the following signature:

std::os::unix::net::datagram::UnixDatagram
pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)>

I would prefer to use an instance of MyStruct to be directly filled by "recv_from" instead to using a u8 buffer and then deserialize the received bytes to the instance struct in order to avoid the copy operation.

In C I would have written something like:

MyStruct my_struct;
recv_from((uint8_t*) &my_struct,sizeof(my_struct));

Is it possible to have something similar also in Rust ?

Thanks

1 Like

You can do this with the zerocopy crate.

use zerocopy::{AsBytes, FromBytes, FromZeroes};

#[derive(AsBytes, FromZeroes, FromBytes)]
#[repr(C, packed)]
pub struct MyStruct {
    fieldA: u8,
    fieldB: u16,
    fieldC: u8,
    fieldD: u32,
}

const MY_STRUCT_SIZE: usize = core::mem::size_of::<MyStruct>();

impl MyStruct {
    pub fn as_slice(&self) -> &[u8; MY_STRUCT_SIZE] {
        zerocopy::transmute_ref!(self)
    }
    
    pub fn as_mut_slice(&mut self) -> &mut [u8; MY_STRUCT_SIZE] {
        zerocopy::transmute_mut!(self)
    }
}

You can also do it without a crate, but that is unsafe.

#[repr(C, packed)]
pub struct MyStruct {
    fieldA: u8,
    fieldB: u16,
    fieldC: u8,
    fieldD: u32,
}

const MY_STRUCT_SIZE: usize = core::mem::size_of::<MyStruct>();

impl MyStruct {
    pub fn as_slice(&self) -> &[u8; MY_STRUCT_SIZE] {
        // SAFETY:
        // * `MyStruct` has the same size as `[u8; MY_STRUCT_SIZE]`.
        // * `[u8; MY_STRUCT_SIZE]` has no alignment requirement.
        // * Since it is packed, this type has no padding.
        unsafe {
            &*(self as *const MyStruct as *const [u8; MY_STRUCT_SIZE])
        }
    }
    
    pub fn as_mut_slice(&mut self) -> &mut [u8; MY_STRUCT_SIZE] {
        // SAFETY:
        // * `MyStruct` has the same size as `[u8; MY_STRUCT_SIZE]`.
        // * `[u8; MY_STRUCT_SIZE]` has no alignment requirement.
        // * Since it is packed, this type has no padding.
        // * All byte values are valid for this type.
        unsafe {
            &mut *(self as *mut MyStruct as *mut [u8; MY_STRUCT_SIZE])
        }
    }
}
10 Likes

Also check bytemuck - Rust

2 Likes

FYI this may be UB depending on the compiler you use since uint8_t may not be excepted from the strict aliasing rule. char/signed char should be preferred instead.

In Rust this is not a problem because there's no strict aliasing rule. You have however to be careful of padding/uninitialized bytes (shouldn't be a problem in your case as others showed).

6 Likes

Neat !

thanks !

I might be misunderstandig something here but isn't this the opposite of what OP wanted, i.e. struct -> &[u8; SIZE] instead of [u8; SIZE] -> Struct?

EDIT
Nevermind, I misunderstood😅

EDIT 2
Notice however, that you can't do this exact thing in rust with the method described above.

MyStruct my_struct;
recv_from((uint8_t*) &my_struct,sizeof(my_struct));

I.e. you can't do

let my_struct: MyStruct;
recv_from(my_struct.as_mut_slice());

Because you'll have to initialize my_struct first in order to be able to get a mutable reference to it. So in a way you don't gain anything from this over just deserialising the struct from a buffer.

Initializing the struct to a constant and then using as_mut_slice is likely cheaper than copying in each field one-by-one. And the code is also a lot easier to read.

Besides, you can reuse my_struct across many calls to recv_from without reinitializing between each call.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.