theicfire
theicfire

Reputation: 3067

Trait mismatch for function argument

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?

Upvotes: 2

Views: 202

Answers (2)

trent
trent

Reputation: 28075

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.

Upvotes: 2

Henri Menke
Henri Menke

Reputation: 10939

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

Upvotes: 2

Related Questions