我有一系列几乎完全相同的函数,只有类型和常量不同。例如:
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在整数中使用泛型的正确方法吗?或者我错过了一种更简单的方法来解决这个问题?
答案 0 :(得分:2)
是和否。
统一处理整数类型并不顺利。
标准库不提供任何&#34;统一&#34;正如你刚刚在这里发现的那样,以统一的方式处理数字的特性。目前还不清楚最佳设计是什么,因此像num
这样的箱子试图探索设计空间。
所以,是的,如果您希望以通用方式处理多个积分,您将不得不拉入外部包(例如num
)或遭受一些痛苦。
但是,您可以使用更简单的代码。
首先,完全定义struct
和trait
是完全没必要的。 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
}
}
注意:如果需要,您可以为u8
,u64
和u128
实施。
这给出了一个更简单的结果:
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
,但是......是的,它并没有让您感觉很棒。