Stewart
Stewart

Reputation: 5062

Iterator types in Rust

I'm learning rust and ran into the problem. I have this MCVE:

fn main() {
    let mut line = String::new();
    std::io::stdin()
        .read_line(&mut line)
        .expect("Failed to read line");

    handle_tokens( line.split_ascii_whitespace() );
}

fn handle_tokens( mut it: std::str::SplitAsciiWhitespace ) {
    loop {
        match it.next() {
            None => return,
            Some(s) => println!("{}",s),
        }
    }
}

String::split_ascii_whitespace returns a SplitAsciiWhitespace object so I've used that in the signature of handle_tokens, but std::str::SplitAsciiWhitespace is an extremely specific type. A generic iterator to a list of strings makes more sense, so that I can choose split_whitespace or maybe just a generic list of strings.

How can I use documentation or compiler errors to generalize the signature of handle_tokens?


Here's my failed attempt to answer the question on my own:

I can see that SplitAsciiWhitespace "Trait Implementations" include:

impl<'a> Iterator for SplitWhitespace<'a>

This is where next() comes from (I had to inspect source code to verify that). Therefore, I tried using an iterator with fn handle_tokens( mut it: Iterator ) { but:

error[E0191]: the value of the associated type `Item` (from trait `std::iter::Iterator`) must be specified
  --> src/main.rs:10:27
   |
10 | fn handle_tokens( mut it: Iterator ) {
   |                           ^^^^^^^^ help: specify the associated type: `Iterator<Item = Type>`

Ok, so Iterator is too generic to use... I need to tell the compiler what it's wrapping. That makes sense, otherwise I wouldn't be able to dereference it. I had to look in the source code again to see how SplitWhitespace implements an Iterator and saw type Item = &'a str; so I tried to specify the Item with fn handle_tokens( mut it: Iterator<Item = &str>), but:

error[E0277]: the size for values of type `(dyn std::iter::Iterator<Item = &str> + 'static)` cannot be known at compilation time
  --> src/main.rs:10:19
   |
10 | fn handle_tokens( mut it: Iterator<Item = &str> ) {
   |                   ^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::iter::Iterator<Item = &str> + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: all local variables must have a statically known size
   = help: unsized locals are gated as an unstable feature

Ok, so I need to specify a size as well. That's strange because while I know the size of str can't be known at compile-time, the size of &str should be.

At this point I'm very stuck. I'm also surprised that source-code inspection is necessary when Rust seems to provide such a great built-in documentation support. That makes me think that the method I'm using to answer this question is wrong.

Upvotes: 5

Views: 1283

Answers (2)

Danil Kondratiev
Danil Kondratiev

Reputation: 96

fn handle_tokens uses fn next from Iterator trait and require Display trait on the items of the Iterator, so you can make this function generic.

use std::fmt::Display;
fn handle_tokens<T>(mut tokens: T)
where
    T: Iterator,
    <T as Iterator>::Item: Display,
{
    loop {
        match tokens.next() {
            None => return,
            Some(s) => println!("{}", s),
        }
    }
}

Or you can .collect() iterator

let tokens = line.split_ascii_whitespace().collect::<Vec<_>>()

I see you tried using dyn. It's called trait objects.


fn handle_tokens3(it: &mut dyn Iterator<Item = &str>) {
    loop {
        match it.next() {
            None => return,
            Some(s) => println!("{}", s),
        }
    }
}

Link to the playground

Upvotes: 3

Kitsu
Kitsu

Reputation: 3445

You at the right path actually. next is indeed defined at the Iterator and that is the one you need to use. The thing you missed is that Iterator is a *trait` actually, not a type. Type can be bounded by a trait, so here generics come handy:

fn handle_tokens<'a, I: Iterator<Item = &'a str>>(mut it: I) { .. }

There's also a special impl-trait syntax which can be used instead:

fn handle_tokens<'a>(mut it: impl Iterator<Item = &'a str>) { .. }

However, the last example cannot be called with an explicitly specified type, i.e. handle_tokens::<SplitAsciiWhitespace>(iter)

Upvotes: 4

Related Questions