I was looking at this question in regard to answering a simple problem I have: I want to have an if/else block to decide whether an iteration should have an accompanying tqdm progress bar:
fn run(n: usize, verbose: bool) {
let it = if verbose { tqdm(0..n) } else { 0..n };
for _ in it { _ }
}
The above code doesn’t compile, because the two arms of the if/else have different types, one is a Range and the other is a Tqdm<Range>. However, based on the linked question at the top I made the following modification and it compiles fine:
fn run(n: usize, verbose: bool) {
let it: Box<dyn impl Iterator<Item = usize>> = if verbose {
Box::new(tqdm(0..n))
} else {
Box::new(0..n)
};
for _ in it { _ }
}
By specifying the type (this is actually necessary, just wrapping it in Box doesn’t help) I can make the code compile. However, I naively feel as though this means there is an extra heap allocation of the dyn Iterator object whereas before everything (at least as I understand) was more or less handled within the stack frame. I know that in general dynamic dispatch (which I think this is but I’m not totally sure) does come with a performance hit.
Before wrapping in Box I did try just annotating as dyn Iterator<Item = usize> but this didn’t work, I guess because somehow the Box abstraction is necessary, but I don’t really understand why.
Is there a way to get this code, or the idea behind it, to compile without the Box and the heap allocation? Is this less important than I think? If this is impossible, why can’t I do the simple dyn Iterator without the Box as I mentioned above?
>Solution :
You’re correct that Box<_> would result in an allocation and that dyn might result in dynamic dispatch.
An non-allocating alternative to Box<dyn _> is &dyn _ (like your simple dyn idea but with correct syntax), but that has to borrow from a location that remains in scope (outside the if or else blocks):
fn run(n: usize, verbose: bool) {
let mut progress;
let mut regular;
let it: &mut dyn Iterator<Item = usize> = if verbose {
progress = tqdm(0..n);
&mut progress
} else {
regular = 0..n;
&mut regular
};
for _ in it { }
}
If you needed to return the iterator, the above approach would not work as you can’t return references to local variables. Instead, you could use the following construction, which also avoids dyn:
fn run_later(n: usize, verbose: bool) -> impl Iterator<Item = usize> + 'static {
let mut progress = None;
let mut regular = None;
if verbose {
progress = Some(tqdm(0..n));
} else {
regular = Some(0..n)
}
progress.into_iter().flatten().chain(regular.into_iter().flatten())
}