【问题标题】:Is there a simple way to use an integral generic type in Rust?有没有一种简单的方法可以在 Rust 中使用整体泛型类型?
【发布时间】:2018-02-26 22:21:49
【问题描述】:

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

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 中为整数使用泛型的正确方法吗?还是我错过了一种更简单的方法来解决这个问题?

【问题讨论】:

    标签: generics rust


    【解决方案1】:

    是和不是。

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

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

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

    你无法抽象掉的一个痛点是 Rust 不允许将文字“无痛”转换为抽象 T。你可以使用num::FromPrimitive,但是……是的,感觉不太好。

    【讨论】:

    • 有没有办法使用 num crate 来避免所有/大部分额外的东西?在我最初的实现中,无论如何我都必须使用它。
    • @Roxy:我对板条箱不太熟悉,所以……也许吧?
    • 很确定您正在寻找Integer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    相关资源
    最近更新 更多