type mismatch with Peekable of Iterator trait object

Viewed 81

This code compiles fine:

use std::iter::Peekable;

struct Token {
    length: u32,
}

fn get_tokens() -> Box<dyn Iterator<Item = Token>> {
    todo!()
}

fn do_something_with_boxed_filter(boxed: Box<dyn Iterator<Item = Token>>) {}

fn main() {
    let tokens = get_tokens();
    let long_tokens = tokens.filter(|token| token.length > 32);
    let boxed = Box::new(long_tokens);
    do_something_with_boxed_filter(boxed);
}

But if I try to make a peekable version, as shown below, it fails to compile.

use std::iter::Peekable;

struct Token {
    length: u32,
}

fn get_tokens() -> Box<dyn Iterator<Item = Token>> {
    todo!()
}

fn do_something_with_peekable(peekable: Peekable<Box<dyn Iterator<Item = Token>>>) {}

fn main() {
    let tokens = get_tokens();
    let long_tokens = tokens.filter(|token| token.length > 32);
    let boxed = Box::new(long_tokens);
    let peekable = boxed.peekable();
    do_something_with_peekable(peekable);
}

The error message is:

error[E0308]: mismatched types
  --> src/main.rs:18:32
   |
15 |     let long_tokens = tokens.filter(|token| token.length > 32);
   |                                     ------------------------- the found closure
...
18 |     do_something_with_peekable(peekable);
   |                                ^^^^^^^^ expected trait object `dyn Iterator`, found struct `Filter`
   |
   = note: expected struct `Peekable<Box<(dyn Iterator<Item = Token> + 'static)>>`
              found struct `Peekable<Box<Filter<Box<dyn Iterator<Item = Token>>, [closure@src/main.rs:15:37: 15:62]>>>`

For more information about this error, try `rustc --explain E0308`.

I don't understand this; the Filter struct implements Iterator so shouldn't I be able to use a Filter struct in place of the dyn Iterator trait object?

1 Answers

Yes, you are right with all the things you said.

The problem is that you can only feed things into functions that can be coerced.

Box<T> is coercible to Box<dyn Trait> if T: Trait. However, at the time of writing, this goes only one level deep. Peekable<Box<T>> can not be coerced into Peekable<Box<dyn Trait>>. Therefore, you need to force the coercion before creating the Peekable:

use std::iter::Peekable;

struct Token {
    length: u32,
}

fn get_tokens() -> Box<dyn Iterator<Item = Token>> {
    todo!()
}

fn do_something_with_peekable(peekable: Peekable<Box<dyn Iterator<Item = Token>>>) {}

fn main() {
    let tokens = get_tokens();
    let long_tokens = tokens.filter(|token| token.length > 32);
    let boxed = Box::new(long_tokens) as Box<dyn Iterator<Item = Token>>;
    let peekable = boxed.peekable();
    do_something_with_peekable(peekable);
}

An alternative would be to use the impl keyword:

use std::iter::Peekable;

struct Token {
    length: u32,
}

fn get_tokens() -> Box<dyn Iterator<Item = Token>> {
    todo!()
}

fn do_something_with_peekable(peekable: Peekable<Box<impl Iterator<Item = Token>>>) {}

fn main() {
    let tokens = get_tokens();
    let long_tokens = tokens.filter(|token| token.length > 32);
    let boxed = Box::new(long_tokens);
    let peekable = boxed.peekable();
    do_something_with_peekable(peekable);
}

With impl, no coercion happens. Box<dyn Trait> is a Box to an unsized trait type, where Box<impl Trait> is actually a real type that implements the Trait. The information what type it is does not get lost to the compiler, impl Trait is actually syntactic sugar for intruducing a generic Box<T> where T: Trait.

impl has advantages and disadvantages. The biggest disadvantage is that just like normal generics, it compiles a new version of the function for every type that gets used with it (I think).

The advantage is that there is no longer the need for a Box at all:

use std::iter::Peekable;

struct Token {
    length: u32,
}

fn get_tokens() -> impl Iterator<Item = Token> {
    vec![].into_iter()
}

fn do_something_with_peekable(peekable: Peekable<impl Iterator<Item = Token>>) {}

fn main() {
    let tokens = get_tokens();
    let long_tokens = tokens.filter(|token| token.length > 32);
    let peekable = long_tokens.peekable();
    do_something_with_peekable(peekable);
}

Just be aware that the types of two objects of Peekable<dyn Trait> are always identical, while the type of two Peekable<impl Trait> objects could be different. Peekable<dyn Trait> is a type, while Peekable<impl Trait> is not a type, it's more of a placeholder for a type. Therefore, you cannot store different objects of Peekable<impl Trait> in one vector, because the vector wouldn't know which type to choose.

Related