Search code examples
rustmoduloboundsrestrictiontype-parameter

In Rust, how can I restrict a generic T to allow modulus?


As an exercism exercise, I'm currently trying to filter an iterator according to whether the value is even in order to produce a new iterator.

My function currently looks like:

pub fn evens<T>(iter: impl Iterator<Item = T>) -> impl Iterator<Item = T>
    where T: std::ops::Rem<Output = T>
{
    iter.filter(|x| x % 2 != 0)
}

Playground

But this won't compile because:

error[E0369]: cannot mod `&T` by `{integer}`
 --> src/lib.rs:4:23
  |
4 |     iter.filter(|x| x % 2 != 0)
  |                     - ^ - {integer}
  |                     |
  |                     &T
  |
help: consider further restricting this bound
  |
2 |     where T: std::ops::Rem<Output = T> + std::ops::Rem<Output = {integer}>
  |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

However, I know I can't simply change this to

pub fn evens<T>(iter: impl Iterator<Item = T>) -> impl Iterator<Item = T>
    where T: std::ops::Rem<Output = T> + std::ops::Rem<Output = {integer}>
{
    iter.filter(|x| x % 2 != 0)
}

Playground

as this fails to compile with:

error: cannot constrain an associated constant to a value
 --> src/lib.rs:2:56
  |
2 |     where T: std::ops::Rem<Output = T> + std::ops::Rem<Output = {integer}>
  |                                                        ------^^^---------
  |                                                        |        |
  |                                                        |        ...cannot be constrained to this value
  |                                                        this associated constant...

I'm vaguely aware of some "Num" traits, but the exercism doesn't seem to accept answers which require importing dependencies through Cargo.toml, so I'm looking for a native/built-in solution.

Any ideas how I can make this work?

(P.S. I've now figured out that I misunderstood the exercise, where "even" describes the enumerated index, not the value... but never mind. I'd still like to know whether/how this could be made to work.)


Solution

  • The easiest way without third party libraries is to convert the constant into T explicitly:

    use core::convert::TryFrom;
    
    pub fn evens<T>(iter: impl Iterator<Item = T>) -> impl Iterator<Item = T>
        where T: Eq + Copy + std::ops::Rem<Output=T> + TryFrom<i32>
    {
        let zero = T::try_from(0).ok().unwrap();
        let two = T::try_from(2).ok().unwrap();
        iter.filter(move |x| *x % two != zero)
    }