1

Having input data of two numbers, I need to construct either an iterator or a range and pass it further:

// ordering is not strictly defined
let x1: usize = 4;
let x2: usize = 2;

let mut x_range = ...;

if x1 < x2 {
    x_range = (x1..=x2);
}
else {
    // i.e if we have ((x1, x2) == (3,1) it should produce 3,2,1 sequence.
    x_range = (x2..=x1).rev();
}

for x in x_range {
// do something
}

The problem is that two variants of ranges produced by regular range expression and rev() have different types and throw type errors further in the code.

So I tried to define the range.


let mut x_range: std::ops::Range<usize>;

But received the error of = note: expected struct std::ops::Range<usize> found struct RangeInclusive<usize>:

Then I tried to define an Iterator and then convert ranges with into_iter().

let mut x_range: dyn Iterator<usize>;

But then I received doesn't have a size known at compile-time

So is there any way to pass an abstract parent interface of an iterator or a range to other functions?

  • 1
    Does this answer your question? https://stackoverflow.com/questions/70238996/dynamically-create-a-range-in-either-direction-in-rust/70241464#70241464 – Joe_Jingyu Jan 05 '22 at 07:10
  • Yeah, that should do the trick! Though I think that vanilla way without external crate from Cormac answer is preferable. – Dmitry Mantis Jan 05 '22 at 07:35

1 Answers1

1

From the Rust Reference:

Due to the opaqueness of which concrete type the value is of, trait objects are dynamically sized types. Like all DSTs, trait objects are used behind some type of pointer; for example &dyn SomeTrait or Box. Each instance of a pointer to a trait object includes:

  • a pointer to an instance of a type T that implements SomeTrait
  • a virtual method table, often just called a vtable, which contains, for each method of SomeTrait and its supertraits that T implements, a pointer to T's implementation (i.e. a function pointer).

The purpose of trait objects is to permit "late binding" of methods. Calling a method on a trait object results in virtual dispatch at runtime: that is, a function pointer is loaded from the trait object vtable and invoked indirectly. The actual implementation for each vtable entry can vary on an object-by-object basis.


The concrete type of dyn Iterator could be any size, so Rust doesn't know how to store it on the stack. You need a level of indirection (a pointer or reference) with a fixed size. In this case, there are a couple probable solutions:

  1. A boxed trait object. This is written like:

    let x_range: Box<dyn Iterator<Item = usize>> = if x1 < x2 {
        Box::new(x1..=x2)
    } else {
        Box::new((x2..=x1).rev())
    };
    

    Rust will move the concrete type to the heap and store a trait object which on the stack (see the relevant chapter in the book). This is generally the simplest option.

  2. An enum with its own Iterator impl. If the set of concrete types is closed and fairly small (as in your case), you can write a type that holds either possible concrete type:

    enum MyIterator {
       Forward(RangeInclusive<usize>),
       Reverse(Rev<RangeInclusive<usize>>),
    }
    
    impl Iterator for MyIterator {
        type Item = usize;
    
        fn next(&mut self) -> Option<Self::Item> {
            match self {
                MyIterator::Forward(ref mut it) => it.next(),
                MyIterator::Reverse(ref mut it) => it.next(),
            }
        }
    }
    
    let x_range = if x1 < x2 {
        MyIterator::Forward(x1..=x2)
    } else {
        MyIterator::Reverse((x2..=x1).rev())
    };
    

    This is a very slightly more efficient solution than the boxed trait object, as it doesn't require a heap allocation and there's one fewer level of indirection to call Iterator::next. That efficiency gain is probably insignificant unless you're calling next() in a very hot loop.

Mac O'Brien
  • 2,407
  • 18
  • 22