Trait mismatch for function argument

2019-07-11 07:40发布

问题:

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?

回答1:

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.



回答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



标签: rust