Преобразование среза целых чисел в срез байтов с нулевым копированием

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 безопасный способ сделать это? В настоящее время я просто использую приведенный выше код, но было бы неплохо избавиться от еще одного небезопасного блока, если смогу. Если нет, то почему? Это небезопасно по какой-то причине, которую я не учел?

Я думаю, что ваш метод несостоятелен из-за проблем с выравниванием и порядком байтов. Что, если T имеет ширину 3 байта, но требует выравнивания по 4 байтам?

cafce25 19.11.2022 22:21

Вы можете взглянуть на ящик bytemuck, чтобы узнать, как это сделать безопасно.

isaactfa 19.11.2022 22:33

@ cafce25, тогда утверждение паникует

Eloff 20.11.2022 23:38

Это должно быть правильно assert!, иначе вы все еще разрешаете UB в безопасном коде.

cafce25 21.11.2022 01:28

Я думаю, что компилятор может оптимизировать утверждения, поскольку они известны во время компиляции. Но крейт bytemuck работает лучше.

Eloff 22.11.2022 02:05
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
0
5
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваша функция ненадежна:

#[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 самой собой.

Да, поэтому комментарий. Я внес некоторые изменения, чтобы затруднить злоупотребление, что вы теперь думаете?

Eloff 20.11.2022 03:47
Ответ принят как подходящий

Ящик 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 20.11.2022 03:33

@Eloff Project Safe Transmute в конечном итоге сделает это возможным. Но я не знаю, сколько времени это займет. И если ваша функция не может проверить все предпосылки к ней, она должна быть небезопасной.

Chayim Friedman 20.11.2022 04:42

Этот проект Safe Transmute кажется мертвым. Но bytemuck работает очень хорошо, я перешел на него.

Eloff 22.11.2022 02:06

Другие вопросы по теме