Requiring `Range<T>` to be iterable: is it expected to be so hard?

I'm trying to make some utility methods which use ranges as iterators. The problem is with the relevant type bounds - the way I've found looks very cumbersome, and I'm not sure if I'm not missing anything.

Basically, the first attempt was something like this:

fn range_id<T>(range: std::ops::Range<T>) -> impl Iterator<Item = T> {
    range
}

This expectedly failed:

error[E0277]: the trait bound `T: Step` is not satisfied
 --> src/lib.rs:1:46
  |
1 | fn range_id<T>(range: std::ops::Range<T>) -> impl Iterator<Item = T> {
  |                                              ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Step` is not implemented for `T`
  |
  = note: required because of the requirements on the impl of `Iterator` for `std::ops::Range<T>`
help: consider restricting type parameter `T`
  |
1 | fn range_id<T: std::iter::Step>(range: std::ops::Range<T>) -> impl Iterator<Item = T> {
  |              +++++++++++++++++

Following this suggestion, however, doesn't help much - the new code errors again:

error[E0658]: use of unstable library feature 'step_trait': recently redesigned
 --> src/lib.rs:1:16
  |
1 | fn range_id<T: std::iter::Step>(range: std::ops::Range<T>) -> impl Iterator<Item = T> {
  |                ^^^^^^^^^^^^^^^
  |

(This raises the question of why such suggestion was ever shown on stable... but that's another topic)

Well, we can try to go another way:

use std::ops::Range;

fn range_id<T>(range: Range<T>) -> impl Iterator<Item = T>
where
    Range<T>: Iterator<Item = T>,
{
    range
}

This code, at least, compiles. However, if we want to do something more tricky...

use std::ops::Range;

fn range_rev<T>(range: Range<T>) -> impl Iterator<Item = T>
where
    Range<T>: Iterator<Item = T> + std::iter::DoubleEndedIterator,
{
    range.rev()
}

...you see that we have to add yet another type bound. And furthermore, to use this function in any generic context, we'll have to reiterate these bounds again and again:

fn use_range_rev<T>(range: Range<T>) {
     // this fails with "T: Step is not implemented",
     // unless we again add _both_ `where` clauses
    range_rev(range);
}

Is this all an expected thing?

Note that the Range<T>: Iterator is in fact more general than T: Step. There aren't currently any implementations of Iterator beyond the T: Step one, but they could potentially be added (e.g. a proposal to add one for *mut T).

So yes, the only supported way is to specify the Range<T>: *Iterator bounds you require. If you're not using the inherent methods, though, (per minimized examples, which ofc are overly simplified) you should prefer just using T: Iterator directly.

1 Like

This usually calls for a new trait with blanket implementation:

// Supertraits are implied bounds
pub trait RangeAndStuff<T>: Iterator<Item=T> + DoubleEndedIterator /* + ... */ {
    // Shadow inherent methods with trait methods too
    fn is_empty(&self) -> bool where T: PartialOrd<T>;
    fn contains<U>(&self, item: &U) -> bool
    where
        T: PartialOrd<T> + PartialOrd<U>,
        U: PartialOrd<T> + ?Sized, 
    ;
}

impl<T> RangeAndStuff<T> for Range<T>
where
    Range<T>: Iterator<Item=T> + DoubleEndedIterator /* + ... */,
{
// Blanket implement for everything you care about
}

// And now your bounds are simpler
pub fn range_rev<T, R: RangeAndStuff<T>>(range: R) -> impl Iterator<Item = T> {
    range.rev()
}
1 Like

Note that this concrete bound can be shortened due to the supertrait relationship between the traits Iterator and DoubleEndedIterator:

use std::ops::Range;
use std::iter::DoubleEndedIterator;

fn range_rev<T>(range: Range<T>) -> impl Iterator<Item = T>
where
    Range<T>: DoubleEndedIterator<Item = T>,
{
    range.rev()
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.