This is a whole lot of red flags for what feels like should be a very common use-case — lending a medium-large C-repr struct of well-behaved constant-size types to functions that accept &[u8] or functions that accept the appropriate &[u8; _].
#[repr(C)]
struct MyExampleStructThatShouldSafelyBorrowToU8Array {
pub t: u8,
pub e: [u8; 1800],
pub c: [u8; 4200]
}
#[inline]
fn struct_mem_ref<'a, T>(x: &'a T) -> &'a [u8; std::mem::size_of::<T>()] {
// FIXME how do I bounds constrain only allowing C-repr structs?
// h/t https://stackoverflow.com/a/27150865/1874170
let ptr = &x as *const _ as *const u8;
let slc = unsafe { std::slice::from_raw_parts(ptr, std::mem::size_of::<T>()) };
array_ref![slc,0,std::mem::size_of::<T>()]
}
How far off the rails am I?
What specific further reading would be useful for making me think about this correctly?
There are no trait bounds for the repr of a struct; the repr of a struct is its own private business except as it documents. In order to cause there to be a suitable trait implemented by the struct, you need a derive macro, and that's exactly what bytemuck does. You should use bytemuck instead; it's an already existing, widely used library encapsulating these unsafe operations.
It's also not sufficient to look for repr(C); you also need to be sure the struct contains no padding, and bytemuck also does that.
Your lifetime annotations are fine, and also, your function would mean exactly the same thing if you omitted them (because the lifetime elision rules give the same result).
There's no way to generically derive the array size from the struct size, without that nightly feature. But you can work with the concrete size as a const item for specific cases.
Also note the bytemuck::bytes_of() function, which works on any bytemuckable type without any bother with declaring the size, but returns a slice reference instead of an array reference.
Hmm. It looks like you're right that I was poorly and incorrectly reimplementing a wheel; bytemuck::must_cast_ref does exactly what I'm looking for.
However, I can't seem to get it to work; I end up with “the trait bound [u8; 1800]: Pod is not satisfied” when I try to apply it to my struct.
Full error
error[E0277]: the trait bound `[u8; 1800]: Pod` is not satisfied
--> src\operations.rs:19:12
|
19 | pub e: [u8; 1800],
| ^^^^^^^^^^ the trait `Pod` is not implemented for `[u8; 1800]`
|
= help: the following other types implement trait `Pod`:
[T; 0]
[T; 1]
[T; 2]
[T; 3]
[T; 4]
[T; 5]
[T; 6]
[T; 7]
and 34 others
note: required by a bound in `operations::_::{closure#0}::check::assert_impl`
--> src\operations.rs:15:23
After including all the appropriate features onto the bytemuck crate, deriving Copy, Clone, bytemuck::Pod, bytemuck::Zeroable on my struct, and giving the compiler a little type hint with an as statement, it seems that the code is at least compiling without issue.
let inst: MyStruct = crate::make_mystruct();
crate::foo_takes_arr(
bytemuck::must_cast_ref(&inst) as &[u8; std::mem::size_of::<MyStruct>()]
);
Now I just need to wait for generic_const_exprs and generic_arg_infer and I'll be able to have an ultra-usable generic reference cast function, I think!
// (no warranties on these functions / :needsreview)
#[inline]
fn mem_ref<T: bytemuck::NoUninit>(inst: &T) -> &[u8; std::mem::size_of::<T>()] {
bytemuck::must_cast_ref(inst) as &[u8; _]
}
#[inline]
fn mem_mut_ref<T: bytemuck::NoUninit + bytemuck::AnyBitPattern>(inst: &mut T) -> &mut [u8; std::mem::size_of::<T>()] {
bytemuck::must_cast_mut(inst) as &mut [u8; _]
}