有没有一种简单的方法可以在Rust中使用整数泛型类型? [英] Is there a simple way to use an integral generic type in Rust?

查看:343
本文介绍了有没有一种简单的方法可以在Rust中使用整数泛型类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一系列功能几乎相同,只是类型和常量不同.例如:

I have a series of functions that are almost identical, differing only in type and a constant. For example:

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

所以我考虑过让该函数在类型上通用:

So I thought about making this function generic on the type:

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

我最终能够实现这一点:

I eventually was able to implement this:

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

这比我预期的要复杂得多.我不得不使用许多特征,定义一个空结构,包括一个外部包装箱,并且使基本计算有些晦涩.当然,它比原来花了很多行.

It was significantly more complicated than I expected. I had to use a bunch of traits, define an empty struct, include an external crate and obscure the basic calculation somewhat. It certainly took many more lines than the original.

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

Is this the right way in Rust to use generics for integers? Or am I missing a much simpler way to approach this?

推荐答案

是,不是.

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

正如您在此处刚刚发现的那样,标准库没有提供用于统一处理数字的任何统一"特征.目前尚不清楚最好的设计是什么,因此像num这样的板条箱试图探索设计空间.

The standard library does not provide any "unifying" trait for handling numbers in a uniform fashion, as you just discovered here. It's unclear what the best design would be, and therefore crates like num have attempted to explore the design space.

是的,是的,如果您希望以通用方式处理多个积分,则必须拉入外部包装箱(例如num)或遭受一些痛苦.

So, yes, if you wish to handle multiple integrals in generic ways, you will have to either pull in external crates (such as num) or suffer some pain.

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

首先,完全不必这样定义structtrait. Rust具有通用功能:

First of all, defining a struct and trait as such is completely unnecessary. Rust has generic functions:

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,因为它确实使此处的代码模糊不清.

Secondly, in the name of readability, I would encourage you NOT to use ToPrimitive::to_u8 directly, as it really obscures the code here.

如果是一次性的,则可以定义变量或将其用途包装到函数中.

If it's a one-off, then you may either define a variable or wrap its use into a function.

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

否则,您可以定义自己的字节选择"特征.现在需要花几行,但要使用一个更清晰的界面来适应您的域.

Otherwise, you can define your own "byte selection" trait. It takes a couple more lines right now, but makes it up with a clearer interface adapted to your domain.

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实施它.

Note: you would implement it for u8, u64 and u128 if necessary.

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

And this gives a simpler-looking result:

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

最后,如果您发现自己一遍又一遍地列举了相同的约束集,请随时为它定义一个新的特征:

Finally, if you find yourself enumerating the same set of constraints over and over, feel free to define a new trait for it:

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
{}

然后使用您的快捷方式:

And then use your shortcut:

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条板箱的整个概念.

Which by the way, is the whole idea of the num crate if I remember correctly.

您无法抽象的一个痛点是Rust不允许将文字无痛地"转换为抽象的T.您可以使用num::FromPrimitive,但是...是的,感觉并不好.

The one pain point you cannot abstract away is that Rust will not allow a literal to be "painlessly" convertible into an abstract T. You can use num::FromPrimitive for that, but... yes, it doesn't exactly feel great.

这篇关于有没有一种简单的方法可以在Rust中使用整数泛型类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆