Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterator types in Rust

Tags:

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.

like image 762
Stewart Avatar asked Jul 16 '20 09:07

Stewart


2 Answers

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)

like image 65
Kitsu Avatar answered Oct 21 '22 02:10

Kitsu


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

like image 28
Danil Kondratiev Avatar answered Oct 21 '22 03:10

Danil Kondratiev