Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust generics on arithmetic operation [duplicate]

Tags:

generics

rust

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.

like image 296
recsubbu Avatar asked Mar 21 '26 21:03

recsubbu


1 Answers

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.

like image 62
Peter Hall Avatar answered Mar 24 '26 21:03

Peter Hall



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!