Search code examples
rusttraits

How to write a function that returns a RangeInclusive or its reverse iterator in stable Rust?


I have this code

use std::iter::Step;

fn main() {
    for i in mkiter(25, 20) {
        println!("{}", i);
    }
    for i in mkiter(10, 23) {
        println!("{}", i);
    }
}

fn mkiter<T>(start: T, end: T) -> Box<dyn Iterator<Item = T>>
where
    T: Step,
{
    if start > end {
        Box::new((end..=start).rev())
    } else {
        Box::new(start..=end)
    }
}

However, the compiler tells me that I am using an unstable feature:

error[E0658]: use of unstable library feature 'step_trait': recently redesigned
 --> main.rs:1:5
  |
1 | use std::iter::Step;
  |     ^^^^^^^^^^^^^^^
  |
  = note: see issue #42168 <https://github.com/rust-lang/rust/issues/42168> for more information

error[E0658]: use of unstable library feature 'step_trait': recently redesigned
  --> main.rs:13:10
   |
13 | where T: Step
   |          ^^^^
   |
   = note: see issue #42168 <https://github.com/rust-lang/rust/issues/42168> for more information

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0658`.

But when I remove the trait bound...

fn main() {
    for i in mkiter(25, 20) {
        println!("{}", i);
    }
    for i in mkiter(10, 23) {
        println!("{}", i);
    }
}

fn mkiter<T>(start: T, end: T) -> Box<dyn Iterator<Item = T>> {
    if start > end {
        Box::new((end..=start).rev())
    } else {
        Box::new(start..=end)
    }
}

...it wants me to add it, which confuses me:

error[E0369]: binary operation `>` cannot be applied to type `T`
  --> main.rs:11:14
   |
11 |     if start > end {
   |        ----- ^ --- T
   |        T
   |
help: consider restricting type parameter `T`
   |
10 | fn mkiter<T: std::cmp::PartialOrd>(start: T, end: T) -> Box<dyn Iterator<Item = T>> {
   |            ++++++++++++++++++++++

error[E0599]: the method `rev` exists for struct `RangeInclusive<T>`, but its trait bounds were not satisfied
   --> main.rs:12:32
    |
12  |         Box::new((end..=start).rev())
    |                                ^^^ method cannot be called on `RangeInclusive<T>` due to unsatisfied trait bounds
    |
   ::: /home/rne/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/range.rs:345:1
    |
345 | pub struct RangeInclusive<Idx> {
    | ------------------------------ doesn't satisfy `RangeInclusive<T>: Iterator`
    |
    = note: the following trait bounds were not satisfied:
            `T: Step`
            which is required by `RangeInclusive<T>: Iterator`
            `RangeInclusive<T>: Iterator`
            which is required by `&mut RangeInclusive<T>: Iterator`
help: consider restricting the type parameter to satisfy the trait bound
    |
10  | fn mkiter<T>(start: T, end: T) -> Box<dyn Iterator<Item = T>> where T: Step {
    |                                                               +++++++++++++

error[E0277]: the trait bound `T: Step` is not satisfied
  --> main.rs:14:9
   |
14 |         Box::new(start..=end)
   |         ^^^^^^^^^^^^^^^^^^^^^ the trait `Step` is not implemented for `T`
   |
   = note: required for `RangeInclusive<T>` to implement `Iterator`
   = note: required for the cast from `RangeInclusive<T>` to the object type `dyn Iterator<Item = T>`
help: consider restricting type parameter `T`
   |
10 | fn mkiter<T: std::iter::Step>(start: T, end: T) -> Box<dyn Iterator<Item = T>> {
   |            +++++++++++++++++

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0277, E0369, E0599.
For more information about an error, try `rustc --explain E0277`.

How do I correctly implement a function that returns a RangeInclusive or its Rev iterator respectively for generic (integer) types in stable Rust?

# rustc --version                                                                                                                                                 2023-05-24 03:34:14
rustc 1.69.0 (84c898d65 2023-04-16)
# rustup update stable                                                                                                                                            2023-05-24 03:34:04
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'

  stable-x86_64-unknown-linux-gnu unchanged - rustc 1.69.0 (84c898d65 2023-04-16)

info: self-update is disabled for this build of rustup
info: any updates to rustup will need to be fetched with your system package manager

Solution

  • Requiring that T: Step is just a means to an end, you don't actually care if T implements Step, you only care that RangeInclusive<T> is an iterator that yields T. And you can express that exactly:

    fn mkiter<T>(start: T, end: T) -> Box<dyn Iterator<Item = T>>
    where
        RangeInclusive<T>: Iterator<Item = T>,
    ...
    

    The compiler still complains about T and Step, but that's only because you need to follow the same principle to make .rev() work by constraining on DoubleEndedIterator as well:

    fn mkiter<T>(start: T, end: T) -> Box<dyn Iterator<Item = T>>
    where
        RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
    ...
    

    Then there's just the loose ends like adding T: PartialOrd so start > end works, and adding a lifetime to bypass the 'static-by-default of returning Box<dyn ...>. Here's the final code:

    fn mkiter<'a, T: 'a>(start: T, end: T) -> Box<dyn Iterator<Item = T> + 'a>
    where
        T: PartialOrd,
        RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
    {
        if start > end {
            Box::new((end..=start).rev())
        } else {
            Box::new(start..=end)
        }
    }