我有一系列几乎相同的函数,仅类型和常量不同。例如:
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
)或遭受一些痛苦。
但是,您可以使用更简单的代码。
首先,定义一个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()
}
否则,您可以定义自己的“字节选择”特征。现在还需要几行代码,但会通过适合您的域的更清晰的界面来弥补。
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 不允许将文字“轻松”转换为抽象 T
。您可以使用 num::FromPrimitive 来实现此目的,但是...是的,它感觉不太好。
关于generics - 有没有一种简单的方法可以在 Rust 中使用整型泛型类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48975698/