generics - 有没有一种简单的方法可以在 Rust 中使用整型泛型类型?

标签 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]
        })
    }
}

这比我预期的要复杂得多。我必须使用一堆特征,定义一个空结构,包含一个外部 crate 并在某种程度上模糊基本计算。它确实比原来花费了更多的行数。

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

最佳答案

是和否。

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

正如您刚刚在此处发现的那样,标准库不提供任何以统一方式处理数字的“统一”特征。目前还不清楚最好的设计是什么,因此像 num 这样的 crate 已经尝试探索设计空间。

所以,是的,如果您希望以通用方式处理多个积分,您将不得不引入外部 crate (例如 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()
}

否则,您可以定义自己的“字节选择”特征。现在还需要几行代码,但会通过适合您的域的更清晰的界面来弥补。

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 不允许将文字“轻松”转换为抽象 T。您可以使用 num::FromPrimitive 来实现此目的,但是...是的,它感觉不太好。

关于generics - 有没有一种简单的方法可以在 Rust 中使用整型泛型类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48975698/

相关文章:

java - 如何在 java 中实例化通用数组类型?

Java - 如何在泛型类中调用我的类 T 的方法

Java 通配符和泛型? super T 和?延伸 T

concurrency - Futures : Rust vs runtime-based languages 的完成与就绪方法

c# - List<T>.Find 抛出 System.IndexOutOfRangeException

rust - 是否有可能在不使用 Box 的情况下返回一个返回闭包的 Rust 闭包?

rust - 在 Rust 中给一个方法作为参数(Vec sort() 方法)

macos - 使用 DTrace 在 Rust 上获取堆栈跟踪/分析数据

xpath - 如何迭代使用 sxd_xpath 提取的节点的所有属性?

java - 我可以有一个接受子类型的列表属性吗?