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>()) }
}
Вот как это было бы написано на C или C++. Выполнять обратное преобразование небезопасно, поскольку выравнивание типов различается. Но преобразование в срез байтов работает, и именно поэтому вы можете преобразовать все в char* в C.
Предоставляет ли Rust безопасный способ сделать это? В настоящее время я просто использую приведенный выше код, но было бы неплохо избавиться от еще одного небезопасного блока, если смогу. Если нет, то почему? Это небезопасно по какой-то причине, которую я не учел?
Вы можете взглянуть на ящик bytemuck, чтобы узнать, как это сделать безопасно.
@ cafce25, тогда утверждение паникует
Это должно быть правильно assert!, иначе вы все еще разрешаете UB в безопасном коде.
Я думаю, что компилятор может оптимизировать утверждения, поскольку они известны во время компиляции. Но крейт bytemuck работает лучше.
Ваша функция ненадежна:
#[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]
}
Как видите, мы можем прочитать ненаписанное до 0 байт между Thingys.
Если вызывающая сторона должна поддерживать некоторые ограничения, функция должна быть unsafe самой собой.
Да, поэтому комментарий. Я внес некоторые изменения, чтобы затруднить злоупотребление, что вы теперь думаете?
Ящик bytemuck — это ящик для таких вещей. Для этого у него есть функция cast_slice():
pub fn get_bytes<T: bytemuck::NoUninit>(array: &[T]) -> &[u8] {
bytemuck::cast_slice(array)
}
Однако ваша функция ненадежна: она позволяет вызывать типы с байтами заполнения, но переинтерпретировать байты заполнения (по сути, uninit), поскольку u8 — это UB. bytemuck::cast_slice() запрещает это, требуя, чтобы тип реализовывал NoUninit. Можно #[derive(NoUninit)] для своих типов, если они удовлетворяют всем требованиям.
Хорошо, что я узнал об этом ящике. Тем не менее кажется странным, что ржавчина не поддерживает что-то подобное в стандартной библиотеке. Я согласен с несостоятельностью, поэтому комментарий гласит: «Используйте только с простыми типами копирования, такими как целые числа, числа с плавающей запятой, логические значения и т. д. Не используйте со структурами или перечислениями». Я просто не знаю, как это реализовать.
@Eloff Project Safe Transmute в конечном итоге сделает это возможным. Но я не знаю, сколько времени это займет. И если ваша функция не может проверить все предпосылки к ней, она должна быть небезопасной.
Этот проект Safe Transmute кажется мертвым. Но bytemuck работает очень хорошо, я перешел на него.
Я думаю, что ваш метод несостоятелен из-за проблем с выравниванием и порядком байтов. Что, если T имеет ширину 3 байта, но требует выравнивания по 4 байтам?