I wanted to construct a more general range which can also count downward. I tried the following
let range = if start < end {
(start..end)
} else {
(end..start).rev()
};
println!("{:?}", range);
which doesn't work because the types aren't right. According to this question I have essentially two options:
Erase the type.
Collect into a vector
Erasing the type:
Something with Box::new but the given the example doesn't actually use .rev():
let items = if start < end {
println!("start is smaller");
Box::new((start..end))
} else {
println!("end is smaller");
Box::new((end..start))
};
And with .rev() it doesn't work.
Collecting into a vector:
let range = if start < end {
(start..end).collect::<Vec<i32>>()
} else {
(end+1..start+1).rev().collect::<Vec<i32>>()
};
This seems to work but now I also need to use .into_iter() so I can map over it again and it might - I don't know - copy thing a round a bit too much.
What's the right way to do this? I don't have a good understanding of what is going on internally so I would like to know both, how to make this efficient and how to make it nice/idiomatic.
let range = if start < end {
Box::new(start..end) as Box<dyn Iterator<Item=usize>>
} else {
Box::new((end..start).rev()) as Box<dyn Iterator<Item=usize>>
};
It can also be written like this:
let range: Box<dyn Iterator<Item=usize>> = if start < end {
Box::new(start..end)
} else {
Box::new((end..start).rev())
};
Your original code doesn't work because Box::new returns Box<T> with the concrete type T you've supplied. But as long as T implements a trait Trait, Box<T> can be coerced to Box<dyn Trait> (a boxed trait object). In the first example the coercion is done explicitly via as. In the second example it's done implicitly.
But more seriously, there needs to be a dynamic dispatch in either case (it is needed to know, for instance, whether to increment or decrement), and whilst the Eitherenum achieves this with a pattern match on the discriminant (isomorphic to having a reverse: bool flag on the range struct), the &dyn Iterator uses a pointer to a vtable to resolve which iteration method to use; in other words, enum-based dispatch is often more performant than trait object / vtable-based dispatch.
Right, I hadn't paid attention to that "detail" either . Indeed, you'd need to use &mut dyn Iterator (or Box<dyn Iterator> like @Riateche suggested if you want ownership, in which case the Either variant becomes even more interesting since it does not require a heap allocation to get ownership).
In my previous example I've failed to use &mut instead of &, but that wasn't relevant. In either case you don't need heap allocation for dynamic dispatch:
I ran into a similar situation with the specs crate and thought @kornel's solution might do the trick but it appears the Join trait doesn't allow that.
Context: !first converts BitSet into BitSetNot, both of which implement Join.
use specs::prelude::*;
fn main() {
let first = BitSet::new();
let second = !first;
let cond = true;
let bitset: &dyn Join = if cond {
&first
} else {
&second
}
bitset.join();
}
Ignoring the fact that &dyn Join is missing its associated types, here's one of the errors I get:
error[E0038]: the trait `specs::join::Join` cannot be made into an object
--> src/main.rs:8:9
|
8 | &first
| ^^^^^^ the trait `specs::join::Join` cannot be made into an object
|
= note: method `get` has no receiver
= note: method `is_unconstrained` has no receiver
= note: required because of the requirements on the impl of `std::ops::CoerceUnsized<&dyn specs::join::Join>` for `&hibitset::BitSet`
But the traits can be made to work, by qualifying the non-object-safe methods with where Self: Sized, if they are reasonably useful without these methods.
That would be a change that specs has to make, though.