2

I've got one piece of Rust code that compiles and one that's very similar that does not.

The one that works:

pub fn do_something(_: Box<Iterator<Item = f64>>) {}

fn main() {
    let iter = Box::new(vec![1.0].into_iter());
    do_something(iter);
}

The one that fails:

pub fn do_something(_: Box<Box<Iterator<Item = f64>>>) {}

fn main() {
    let iter = Box::new(Box::new(vec![1.0].into_iter()));
    do_something(iter);
}

The difference is I have a Box<Box<..>> instead of a Box<..>

I get the following error:

error[E0308]: mismatched types
 --> src/main.rs:5:18
  |
5 |     do_something(iter);
  |                  ^^^^ expected trait std::iter::Iterator, found struct `std::vec::IntoIter`
  |
  = note: expected type `std::boxed::Box<std::boxed::Box<std::iter::Iterator<Item=f64> + 'static>>`
             found type `std::boxed::Box<std::boxed::Box<std::vec::IntoIter<{float}>>>`

I'm interpreting this error to say "IntoIter does not have the trait Iterator" .. but it does. What's the issue?

trent
  • 25,033
  • 7
  • 51
  • 90
theicfire
  • 2,719
  • 2
  • 26
  • 29
  • 1
    Possible duplicate of [Why does Rust not allow coercion to trait objects inside containers?](https://stackoverflow.com/questions/41889727/why-does-rust-not-allow-coercion-to-trait-objects-inside-containers) Although that question does not have an answer that explains how to fix it (add `as Box>`). – trent Jan 12 '18 at 01:43
  • [Demonstration](https://play.rust-lang.org/?gist=803a4c732b7ad34363d60907960834da&version=stable). – trent Jan 12 '18 at 01:47

2 Answers2

2

To be honest, I'm no expert in Rust at all, but my expectation would have been that both of the snippets you show do not compile. That is because, as you pointed out, Iterator is a trait and not a type and basically you want do_something to receive any type which implements Iterator. Maybe there exists a shortcut such that the compiler can transform the signature into a generic if one of the types is a trait which could be why is sometimes works, but then I'm also not familiar with the Rust language specification enough.

Instead of having do_something take something of type Iterator (?) make it a generic of type T where T is trait bound.

pub fn do_something<T>(_: Box<Box<T>>) 
    where T: Iterator<Item = f64> + Send {}

fn main() {
    let iter = Box::new(Box::new(vec![1.0].into_iter()));
    do_something(iter);
}

Playground

Alternatively, you constrain do_something entirely to std::vec::IntoIter and only take parameters of that type.

pub fn do_something(_: Box<Box<std::vec::IntoIter<f64>>>) {}

fn main() {
    let iter = Box::new(Box::new(vec![1.0].into_iter()));
    do_something(iter);
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Henri Menke
  • 10,705
  • 1
  • 24
  • 42
  • `Box>` is the type of a boxed object of unknown size that implements `Iterator` via dynamic dispatch. In Rust 1.27 and later, [this can be written `Box>`](https://stackoverflow.com/questions/50650070/what-does-dyn-mean-in-a-type), which is more explicit about the difference between the type and the trait. – trent Jun 20 '18 at 14:26
2

You can't coerce a Box<Box<I>> into a Box<Box<Iterator<Item = f64>>>, for reasons discussed in this question, but you can coerce the inner Box:

pub fn do_something(_: Box<Box<Iterator<Item = f64>>>) {}

fn main() {
    let iter = Box::new(Box::new(vec![1.0].into_iter()) as Box<Iterator<Item = f64>>);
    //                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    do_something(iter);
}

Playground.

This works because a cast is a coercion site. By writing as Box<Iterator<Item = f64>>, you're hinting to the compiler that it should attempt to make the expression to the left fit that type instead of inferring Box<IntoIter<f64>>, because once it's wrapped up in the "outer" Box, you can't change it anymore.

Alternatively (but less clearly), you could make Box::new(...) a coercion site by explicitly parameterizing Box:

    let iter = Box::<Box<Iterator<Item = f64>>>::new(Box::new(vec![1.0].into_iter()));

Which effectively does the same thing.

trent
  • 25,033
  • 7
  • 51
  • 90
  • I don't think the explanation is given in the question you linked. You need a coercion in both code snippets given in the question. However, in the first case the coercion needs to happen at a coercion site, so it happens implicitly (function arguments are coercion sites). In the second case, the coercion needs to happen inside the call to `Box::new()`, and there is no reason for Rust to implicitly apply the coercion in that place. See the [chapter on coercions in the nomicon](https://doc.rust-lang.org/nomicon/coercions.html) for more details. – Sven Marnach Jan 12 '18 at 11:34
  • @SvenMarnach I'm not sure I understand your objection. The argument given in the other question is that you can't coerce a `Vec>` to a `Vec>`: the outer layer would have to be reallocated. The logic applies equally well to `Box`. As for coercion sites -- well, that's why I put the cast there. – trent Jan 12 '18 at 14:39
  • I see where you are coming from. There is definitely some overlap between the questions. I just think the focus is different here; the question is more or less why the first version works while the second version doesn't. The answer to that question in my opinion is to explain what coercions happen explicitly and what coercions happen implicitly. The other question deals with why you can't do an explicit cast on the outer level, and explains why the language has been designed that way. So yeah, I was looking at it from a different angle, but what you say makes sense, too. – Sven Marnach Jan 12 '18 at 14:58