Search code examples
rustrust-embedded

Enforcing trait implemented only on given type size in Rust


For embedded/IO use case in Rust, I need to build a lib to receive/transmit messages using a small fixed size buffer (must not exceed it). The messages themselves will be defined by the user of the lib.

Is there a way to define a Rust trait or a generic constraint to ensure that each user message is #[repr(c,packed)] and has a specific size? Or am I approaching it wrong?

// "global" data, per ISO specification must have <= 10 bytes
type BufferData = [u8; 10];
global_buffer: BufferData

// A user would define these messages that must fit in 10B

// This message 
#[repr(c, packed)]
struct MessageA {
  pub message_id: u8,
  pub some_flag: u8,
  pub some_data: [u8; 2],
  _padding: [u8; 6], // msg uses 4 bytes, so must have padding
}

#[repr(c, packed)]
struct MessageB {  // uses the whole 10 bytes, no padding
  pub message_id: u8,
  pub some_data: [u8; 9],
}

Elsewhere in code, I fill out these messages and transmute them (I might be using unsafe incorrectly here?):

{
  let ptr = &mut global_buffer;
  let msg = unsafe { &mut *(ptr as *mut BufferData as *mut MessageB) };
  *msg = MessageB { message_id = 0x1, some_flag = 0x10, ... };
}
transmit_buffer(&global_buffer);

Solution

  • I don't think you can do exactly that, but instead you can just before the unsafe code that assumes the type is of certain size, call a function that will fail at compile-time if the size is not as expected:

    #[inline(always)]
    const fn verify_size<T, const EXPECTED_SIZE: usize>() {
        struct Foo<T, const EXPECTED_SIZE: usize>(T);
        impl<T, const EXPECTED_SIZE: usize> Foo<T, EXPECTED_SIZE> {
            const VERIFY: () = if core::mem::size_of::<T>() != EXPECTED_SIZE {
                panic!("invalid size");
            };
        }
        
        Foo::<T, EXPECTED_SIZE>::VERIFY
    }
    
    fn assume_size_unsafe<T>() {
        verify_size::<T, 4>();
        
        unsafe {
            // ... do unsafe work assuming size_of::<T>() is 4 bytes
        }
    }