有没有一种简单的方法在Rust中使用整数泛型类型?

时间:2018-02-25 16:22:20

标签: generics rust

我有一系列几乎完全相同的函数,只有类型和常量不同。例如:

fn update16(table: &[u16], init: u16, xs: &[u8]) -> u16 {
    xs.iter().fold(init, |acc, x| { (acc << 8) ^ table[(((acc >> 8) as u8) ^ x) as usize] })
}

fn update32(table: &[u32], init: u32, xs: &[u8]) -> u32 {
    xs.iter().fold(init, |acc, x| { (acc << 8) ^ table[(((acc >> 24) as u8) ^ x) as usize] })
}

所以我考虑在类型上使这个函数通用:

trait Update<T> {
    fn update(table: &[T], init: T, xs: &[u8]) -> T;
}

我最终能够实现这一点:

use std::ops::Shl;
use std::ops::Shr;
use std::ops::BitXor;
use std::mem::size_of;

extern crate num;
use num::ToPrimitive;

struct Normal;

impl<
    T: Copy + Shl<u8, Output = T> + Shr<usize, Output = T> + BitXor<Output = T> + ToPrimitive,
> CrcUpdate<T> for Normal {
    fn update(table: &[T], init: T, xs: &[u8]) -> T {
        xs.iter().fold(init, |acc, x| {
            (acc << 8) ^
                table[(ToPrimitive::to_u8(&(acc >> ((8 * size_of::<T>()) - 8))).unwrap() ^ x) as
                          usize]
        })
    }
}

这比我预期的要复杂得多。我不得不使用一堆特征,定义一个空结构,包含一个外部包,并在某种程度上模糊基本计算。它肯定比原来的线路要多得多。

这是Rust在整数中使用泛型的正确方法吗?或者我错过了一种更简单的方法来解决这个问题?

1 个答案:

答案 0 :(得分:2)

是和否。

统一处理整数类型并不顺利。

标准库不提供任何&#34;统一&#34;正如你刚刚在这里发现的那样,以统一的方式处理数字的特性。目前还不清楚最佳设计是什么,因此像num这样的箱子试图探索设计空间。

所以,是的,如果您希望以通用方式处理多个积分,您将不得不拉入外部包(例如num)或遭受一些痛苦。

但是,您可以使用更简单的代码。

首先,完全定义structtrait是完全没必要的。 Rust具有通用功能:

fn update<T>(table: &[T], init: T, xs: &[u8]) -> T
where
    T: Copy + Shl<u8, Output = T> + Shr<usize, Output = T> + BitXor<Output = T> + ToPrimitive,
{
    xs.iter().fold(init, |acc, x| {
        (acc << 8)
            ^ table[(ToPrimitive::to_u8(&(n >> ((8 * size_of::<T>()) - 8))).unwrap() ^ x) as usize]
    })
}

其次,以可读性的名义,我建议您不要直接使用ToPrimitive::to_u8,因为它实际上模糊了这里的代码。

如果它是一次性的,那么你可以定义一个变量或将其用途包装成一个函数。

fn upper8<T>(n: T) -> u8 {
    ToPrimitive::to_u8(&(n>> ((8 * size_of::<T>()) - 8))).unwrap()
}

否则,您可以定义自己的&#34;字节选择&#34;特征。它现在需要更多行,但是可以通过适合您域名的更清晰的界面来实现。

trait SelectByte: Sized {
    fn bytes(&self) -> usize { mem::size_of::<Self>() }
    fn lower(&self, n: usize) -> u8;
    fn upper(&self, n: usize) -> u8 { self.lower(self.bytes() - n - 1) }
}

impl SelectByte for u16 {
    fn lower(&self, n: usize) -> u8 {
        assert!(n <= 1);
        ((*self >> (n * 8)) & 255u16) as u8
    }
}

impl SelectByte for u32 {
    fn lower(&self, n: usize) -> u8 {
        assert!(n <= 3);
        ((*self >> (n * 8)) & 255u32) as u8
    }
}

注意:如果需要,您可以为u8u64u128实施。

这给出了一个更简单的结果:

fn update<T>(table: &[T], init: T, xs: &[u8]) -> T
where
    T: Copy + Shl<u8, Output = T> + BitXor<Output = T> + SelectByte,
{
    xs.iter().fold(init, |acc, x| {
        (acc << 8) ^ table[(acc.upper(0) ^ x) as usize]
    })
}

最后,如果您发现自己一遍又一遍地枚举同一组约束,请随意为它定义一个新特征:

trait Numeric: Copy + Shl<u8, Output = Self> + BitXor<Output = Self> + SelectByte {}

impl<T> Numeric for T
    where T: Copy + Shl<u8, Output = T> + BitXor<Output = T> + SelectByte
{}

然后使用您的快捷方式:

fn update<T: Numeric>(table: &[T], init: T, xs: &[u8]) -> T {
    xs.iter().fold(init, |acc, x| { (acc << 8) ^ table[(acc.upper(0) ^ x) as usize] })
}

顺便提一句,如果我没记错的话,那就是num箱子的整体想法。

你无法抽象出来的一个痛点就是Rust不会让文字无法轻松地#34;可转换为抽象T。您可以使用num::FromPrimitive,但是......是的,它并没有让您感觉很棒。