3

I wrote a generic function to check if a given number is even or not:

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
}

It produces the compiler error:

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

Since this is an error on using T with concrete i32 values (2 and 0), I wrote another version of is_even like this:

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

This produces the desired output, but using is_even is convoluted now: is_even(x, 2, 0). How to make my original version of is_even work?

I do recognise that the modulo operator is generic enough and works with u16 and u32 values directly; a function like is_even is not needed - but I wrote it to understand how generics work.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
recsubbu
  • 35
  • 5

1 Answers1

5

You are making a lot more assumptions about your T than you are expressing in the types. You have correctly constrained T to Rem and PartialEq, so you can use the % and == operators, but you are also assuming that there are special values 0 and 2 which are of that type.

There is no trait for the existence of 2, but you can use the num-traits crate to find a trait for types that have a 0 and a 1. Assuming that the generic type also has addition, you can ensure that it has 2 as well by adding one to itself.

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

Note, I also made T: Copy so that it didn't require references in the addition expression. This is a reasonable assumption for most numeric types.


Without using a third party crate, you can also use the values of 0 and 2 from another type, for example u8, and just ensure that your generic type can be converted to from that.

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
}

I like this version less because the constraint T: From<u8> doesn't really communicate anything useful about what the function is doing. It ties the type to an esoteric implementation detail, while the first version's constraints precisely describe the arithmetic operations that need to be supported.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Liked your native version better, Peter. Thanks much. – recsubbu Jun 09 '18 at 13:35
  • Got that, Peter. Liked the native version because it seems to have an edge when it comes to modulo by a larger number instead of 2. First version means `one + one + ... + one` n times. – recsubbu Jun 11 '18 at 11:29
  • @recsubbu The `num` crate has the trait [`FromPrimitive`](https://rust-num.github.io/num/num/trait.FromPrimitive.html) that you may find interesting – user25064 Jun 11 '18 at 12:05