关于算术运算的Rust泛型

时间:2018-06-09 10:35:30

标签: generics rust

我写了一个通用函数来检查给定数字是否是偶数:

use std::ops::Rem;

fn main() {
    let x: u16 = 31;
    let y: u32 = 40;

    println!("x = {}, y = {}", is_even(x), is_even(y));
}

fn is_even<T: Rem<Output = T> + PartialEq>(n: T) -> bool {
    n % 2 == 0
}

它产生编译器错误:

error[E0308]: mismatched types
  --> src/main.rs:11:9
   |
11 |     n % 2 == 0
   |         ^ expected type parameter, found integral variable
   |
   = note: expected type `T`
              found type `{integer}`

error[E0308]: mismatched types
  --> src/main.rs:11:14
   |
11 |     n % 2 == 0
   |              ^ expected type parameter, found integral variable
   |
   = note: expected type `T`
              found type `{integer}`

由于将T与具体i32值(2和0)一起使用时出错,我写了is_even的另一个版本,如下所示:

fn is_even<T: Rem<Output = T> + PartialEq> (n: T, with: T, output: T) -> bool {
    n % with == output
}

这会产生所需的输出,但现在使用is_even进行复杂处理:is_even(x, 2, 0)。如何让is_even的原始版本正常工作?

我确实认为模运算符足够通用,可直接使用u16u32值;不需要像is_even这样的函数 - 但是我写这篇函数是为了理解泛型的工作原理。

1 个答案:

答案 0 :(得分:3)

您对T的假设比在类型中表达的要多得多。您已将T正确约束为RemPartialEq,因此您可以使用%==运算符,但您也假设有特殊值02属于该类型。

2的存在没有特征,但您可以使用num-traits包来查找具有01的类型的特征。假设泛型类型也有添加,您可以通过向自身添加2来确保它具有one

extern crate num_traits;

use num_traits::{Zero, One};
use std::ops::{Rem, Add};

fn main() {
    let x: u16 = 31;
    let y: u32 = 40;

    println!("x = {}, y = {}", is_even(x), is_even(y));
}

fn is_even<T> (n: T) -> bool 
where
    T: Rem<Output = T> + PartialEq + One + Zero + Add + Copy
{
    let one: T = One::one();
    n % (one + one) == Zero::zero()
}

注意,我还提出T: Copy,因此它不需要在加法表达式中引用。对于大多数数字类型,这是一个合理的假设。

如果不使用第三方广告资源,您还可以使用其他类型的02值,例如u8,并确保您的通用类型可以转换从那以后。

fn is_even<T> (n: T) -> bool 
where
    T: Rem<Output = T> + PartialEq + From<u8>
{
    let two: T = 2u8.into();
    let zero: T = 0u8.into();
    n % two == zero
}

我更喜欢这个版本,因为约束T: From<u8>并没有真正传达有关函数正在做什么的任何有用信息。它将类型与深奥的实现细节联系起来,而第一个版本的约束则精确地描述了需要支持的算术运算。