Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Do I have to allocate with `Box` or similar in order for different types which share an trait to be accepted by the compiler?

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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())
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading