Reputation: 21656
How to convert e.g. &[u64]
to &[u8]
? I contend that it's safe to do with this method (edited to make harder to misuse):
use num_traits::PrimInt;
/// Reinterpret a slice of T as a slice of bytes without copying.
/// Only use with simple copy types like integers, floats, bools, etc. Don't use with structs or enums.
pub fn get_bytes<T: PrimInt>(array: &[T]) -> &[u8] {
// Add some checks to try and catch unsound use
debug_assert!(size_of::<T>() <= 16);
debug_assert!(size_of::<T>().is_power_of_two());
debug_assert_eq!(size_of::<T>(), align_of::<T>());
// Safety: &[u64] can be safely converted to &[u8]
// (so why doesn't rust have a safe method for this?)
unsafe { std::slice::from_raw_parts(array.as_ptr() as *const u8, array.len() * std::mem::size_of::<T>()) }
}
That's how it would be written in C or C++. It's not safe to do the inverse conversion, because the alignment of the types differs. But casting down into a slice of bytes works, and it's why you can cast everything to char*
in C.
Does Rust expose a safe method to do this? I'm currently just using the code above, but it'd be nice to get rid of one more unsafe block if I can. If not, why not? Is it unsafe for some reason I haven't considered?
Upvotes: 0
Views: 1045
Reputation: 70860
The bytemuck
crate is the crate for this kind of things. It has the cast_slice()
function for that:
pub fn get_bytes<T: bytemuck::NoUninit>(array: &[T]) -> &[u8] {
bytemuck::cast_slice(array)
}
However, your function is unsound: it allows calling with types with padding bytes, but reinterpreting padding bytes (essentially uninit
) as u8
is UB. bytemuck::cast_slice()
prohibits this by requiring the type to implement NoUninit
. You can #[derive(NoUninit)]
for your types, as long as they satisfy all requirements.
Upvotes: 3
Reputation: 27227
Your function is unsound:
#[derive(Debug, Clone, Copy)]
struct Thingy {
a: u32,
b: u16,
}
/// Reinterpret a slice of T as a slice of bytes without copying.
/// Only use with simple copy types like integers, floats, bools, etc. Don't use with structs or enums.
pub fn get_bytes<T: Copy>(array: &[T]) -> &[u8] {
// Safety: &[u64] can be safely converted to &[u8]
// (so why doesn't rust have a safe method for this?)
unsafe { std::slice::from_raw_parts(array.as_ptr() as *const u8, array.len() * std::mem::size_of::<T>()) }
}
fn main() {
let a = [Thingy {
a: 0xca_fc_e2_50,
b: 0x12_34,
}, Thingy {
a: 0x98_76_54_32,
b: 0xca_fc,
}];
let b: &[u8] = get_bytes(&a);
println!("{:?}", b);
// [80, 226, 252, 202, 52, 18, 0, 0, 50, 84, 118, 152, 252, 202, 0, 0]
}
As you can see we can read the unwritten to 0 bytes between the Thingy
s
If the caller has to hold up some constraints the function should be unsafe
itself.
Upvotes: 0