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

1

7

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.)

Wardwarde answered 2/9, 2021 at 14:48 Comment(12)
Does this answer your question? How can I add 1 to a generic T?Coinstantaneous
docs.rs/num/0.4.0/num/trait.Integer.html#tymethod.mod_floorCoinstantaneous
@SvetlinZarev, assuming it is possible, I'd like to know how to do this without depending upon any crates which don't ship with rust.Wardwarde
The answer is the same as the one I've given in the duplicate question - implement a trait which gives you the mod operation.Coinstantaneous
@SvetlinZarev, unless I'm missing or misunderstanding something, I don't understand how implementing a new trait would be the way to go. I want to use the method/operator which already exists on integer and possibly other types, not require that these types should need to create new redundant implementations to fulfill the requirements of the new trait.Wardwarde
@BrianKessler on generic types you can invoke only operations provided by trait-bounds. If your T is not constrained by a trait, then you cannot do any operations with it nor call any methods on it. So after we cleared that we need a trait, then comes the hard part - which trait ? In rust only some operators can be overloaded and % can be overloaded by the Rem trait: doc.rust-lang.org/std/ops/trait.Rem.html. So you need fn foo<T: Rem>(t: T)Coinstantaneous
But then you need to convert that 2 to a T :) so you need another trait for that and it is not available in stdCoinstantaneous
Without external crates or new traits, it can be done more or less with this code.Frenchy
@rodrigo, Are you aware your code does not compile?Wardwarde
@BrianKessler: I forgot to click the share button and shared an old version, :-(. I've updated the comment, with a version that does work for i8 too.Frenchy
@rodrigo, that's ugly, but it works! :-) I would have accepted that as an answer, but orlp's below is a little more readable. ;-)Wardwarde
@BrianKessler, hey any progress, I have tried rodrigo's code which works on vs_code, but it does not compile in exercismAnalogize
F
8

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)
}
Fastidious answered 2/9, 2021 at 15:42 Comment(6)
Wow! That's much more ugly and complicated than I would have expected, but it makes sense. :-) Thanks!Wardwarde
why use try_from just to unwrap it, instead of simply From?Incongruous
@Incongruous consider the case where T is i16, From<i32> is not implemented for i16 since not all i32 values can be converted into i16.Punchdrunk
You could use From<u8> instead, which is implemented for every integer type except for i8.Vanillin
@kmdreko, so then it will not compile and you will know it. Better than knowing it in runtime when you do the unwrap.Incongruous
@Netwave: But there are no standard types where the try_from(0u8) or try_from(2u8) will fail, so you should be safe.Frenchy

© 2022 - 2024 — McMap. All rights reserved.