Невозможно создать универсальную функцию, которая преобразует часть байтов в целое число, потому что размер не известен во время компиляции - PullRequest
2 голосов
/ 02 октября 2019

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

fn i_from_slice<T>(slice: &[u8]) -> Option<T>
where
    T: Sized,
{
    match slice.len() {
        std::mem::size_of::<T>() => {
            let mut buf = [0; std::mem::size_of::<T>()];
            buf.copy_from_slice(slice);
            Some(unsafe { std::mem::transmute_copy(&buf) })
        }
        _ => None,
    }
}

Руст не позволит мне сделать это:

error[E0532]: expected tuple struct/variant, found function `std::mem::size_of`
 --> src/lib.rs:6:9
  |
6 |         std::mem::size_of::<T>() => {
  |         ^^^^^^^^^^^^^^^^^^^^^^ not a tuple struct/variant

error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/lib.rs:7:31
  |
7 |             let mut buf = [0; std::mem::size_of::<T>()];
  |                               ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `T`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = help: consider adding a `where T: std::marker::Sized` bound
  = note: required by `std::mem::size_of`

Есть ли способ, которым я могу статически узнать размер T?

Ответы [ 3 ]

4 голосов
/ 02 октября 2019

Если ваш T является целым числом, вам не нужен небезопасный код, так как есть from_ne_bytes.

Если вам абсолютно нужна универсальная функция, вы можетедобавить черту:

use std::convert::TryInto;

trait FromBytes: Sized {
    fn from_ne_bytes_(bytes: &[u8]) -> Option<Self>;
}

impl FromBytes for i32 {
    fn from_ne_bytes_(bytes: &[u8]) -> Option<Self> {
        bytes.try_into().map(i32::from_ne_bytes).ok()
    }
}

// Etc. for the other numeric types.

fn main() {
    let i1: i32 = i_from_slice(&[1, 2, 3, 4]).unwrap();
    let i2 = i32::from_ne_bytes_(&[1, 2, 3, 4]).unwrap();

    assert_eq!(i1, i2);
}

// This `unsafe` usage is invalid, but copied from the original post
// to compare the result with my implementation.
fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
    if slice.len() == std::mem::size_of::<T>() {
        Some(unsafe { std::mem::transmute_copy(&slice[0]) })
    } else {
        None
    }
}
2 голосов
/ 02 октября 2019

Вам не нужен промежуточный буфер, вы можете вызвать transmute_copy непосредственно на входном срезе. Более того, как отметил @BenjaminLindley в комментариях, вам нужно убедиться, что вы трансмутируете из первого элемента в слайсе, а не в толстый указатель, который является самим слайсом:

fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
    if slice.len() == std::mem::size_of::<T>() {
        Some(unsafe { std::mem::transmute_copy(&slice[0]) })
    } else {
        None
    }
}
2 голосов
/ 02 октября 2019

Есть ли способ, которым я могу статически знать размер T?

Да, вы знаете размер во время компиляции. Но размер может варьироваться и не является постоянным. Вместо использования массива фиксированного размера вы можете использовать вектор, который является непрерывным растущим массивом.

Кроме того, Sized - это признак отказа от маркера. Все параметры типа имеют неявную границу Sized. Вам не нужно излагать этот факт.

Вам нужен защитный ограждение, чтобы использовать шаблон, совпадающий с тем, что вы делали, но здесь проще использовать выражение if-else.

В общем, это работает:

fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
    let n = std::mem::size_of::<T>();
    if slice.len() == n {
        let mut buf = vec![0; n];
        buf.copy_from_slice(slice);
        Some(unsafe { std::mem::transmute_copy(&buf) })
    } else {
        None
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...