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);
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
}
}