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

Brian Kessler
  • 2,187
  • 6
  • 28
  • 58
  • Does this answer your question? [How can I add 1 to a generic T?](https://stackoverflow.com/questions/56526677/how-can-i-add-1-to-a-generic-t) – Svetlin Zarev Sep 02 '21 at 15:17
  • https://docs.rs/num/0.4.0/num/trait.Integer.html#tymethod.mod_floor – Svetlin Zarev Sep 02 '21 at 15:19
  • @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. – Brian Kessler Sep 02 '21 at 15:21
  • The answer is the same as the one I've given in the duplicate question - implement a trait which gives you the mod operation. – Svetlin Zarev Sep 02 '21 at 15:22
  • @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. – Brian Kessler Sep 02 '21 at 15:26
  • @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: https://doc.rust-lang.org/std/ops/trait.Rem.html. So you need `fn foo(t: T)` – Svetlin Zarev Sep 02 '21 at 15:30
  • But then you need to convert that `2` to a `T` :) so you need another trait for that and it is not available in `std` – Svetlin Zarev Sep 02 '21 at 15:37
  • Without external crates or new traits, it can be done more or less with this [code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=594af830411d5878f3ef983f8be0a3c6). – rodrigo Sep 02 '21 at 15:37
  • @rodrigo, Are you aware your code does not compile? – Brian Kessler Sep 02 '21 at 15:40
  • @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. – rodrigo Sep 02 '21 at 15:42
  • @rodrigo, that's ugly, but it works! :-) I would have accepted that as an answer, but orlp's below is a little more readable. ;-) – Brian Kessler Sep 02 '21 at 15:49
  • @BrianKessler, hey any progress, I have tried rodrigo's code which works on vs_code, but it does not compile in exercism – Vimal Kurien Sam Apr 02 '22 at 14:16

1 Answers1

7

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)
}
orlp
  • 112,504
  • 36
  • 218
  • 315
  • 1
    Wow! That's much more ugly and complicated than I would have expected, but it makes sense. :-) Thanks! – Brian Kessler Sep 02 '21 at 15:49
  • why use `try_from` just to unwrap it, instead of simply `From`? – Netwave Sep 02 '21 at 16:06
  • 1
    @Netwave consider the case where `T` is `i16`, `From` is not implemented for `i16` since not all `i32` values can be converted into `i16`. – kmdreko Sep 02 '21 at 16:19
  • 2
    You could use `From` instead, which is implemented for every integer type except for `i8`. – Kevin Reid Sep 02 '21 at 16:27
  • @kmdreko, so then it will not compile and you will know it. Better than knowing it in runtime when you do the unwrap. – Netwave Sep 02 '21 at 16:35
  • 2
    @Netwave: But there are no standard types where the `try_from(0u8)` or `try_from(2u8)` will fail, so you should be safe. – rodrigo Sep 02 '21 at 16:49