14
votes

I'm interested in peeking ahead in a character stream. To my understanding, Peekable would be the way to go. I can't quite figure out how to use it.

First attempt:

fn trawl<I, E>(pk: &mut I) where I: std::iter::Peekable<Result<char, E>> {
    loop {
        let cur = pk.next();
        let nxt = pk.peek();
        match (cur, nxt) {
            (Some(i), Some(nxt_i)) => println!("{} {}", i.ok(), nxt_i.ok()),
            _ => (),
        }
    }
}

fn main() {
    trawl(&mut std::io::stdio::stdin().chars());
}

This fails to compile with

> rustc /tmp/main.rs
/tmp/main.rs:1:37: 1:73 error: `std::iter::Peekable` is not a trait
/tmp/main.rs:1 fn trawl<I, E>(pk: &mut I) where I: std::iter::Peekable<Result<char, E>> {
                                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error

Okay, fair enough. I don't fully understand traits yet so I try to pass an iterator in and then create a peekable version:

fn trawl<I, E>(it: &mut I) where I: Iterator<Result<char, E>> {
    let mut pk = it.peekable();
    loop {
        let cur = pk.next();
        let nxt = pk.peek();
        match (cur, nxt) {
            (Some(i), Some(nxt_i)) => println!("{} {}", i.ok(), nxt_i.ok()),
            _ => (),
        }
    }
}

fn main() {
    trawl(&mut std::io::stdio::stdin().chars().peekable());
}

This fails with

> rustc /tmp/main.rs
/tmp/main.rs:2:18: 2:20 error: cannot move out of dereference of `&mut`-pointer
/tmp/main.rs:2     let mut pk = it.peekable();
                                ^~
/tmp/main.rs:7:65: 7:70 error: cannot move out of dereference of `&`-pointer
/tmp/main.rs:7             (Some(i), Some(nxt_i)) => println!("{} {}", i.ok(), nxt_i.ok()),
                                                                               ^~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
/tmp/main.rs:7:39: 7:77 note: expansion site
error: aborting due to 2 previous errors

Could someone explain:

  • why Peekable couldn't appear in the function type for lack of being a trait,
  • what the compiler means when it says 'move out of dereference of' and
  • how I might resolve either or both?

A third version

fn trawl<I, E>(mut it: I) where I: Iterator<Result<char, E>> {
    let mut pk = it.peekable();
    loop {
        let cur = pk.next();
        let nxt = pk.peek();
        match (cur, nxt) {
            (Some(i), Some(nxt_i)) => println!("{} {}", i.ok(), nxt_i.ok()),
            // (Some(i), ) => println!("{}", i.ok()),
            _ => (),
        }
    }
}

fn main() {
    trawl(std::io::stdio::stdin().chars().peekable());
}

This fails with:

> rustc /tmp/main.rs
/tmp/main.rs:7:65: 7:70 error: cannot move out of dereference of `&`-pointer
/tmp/main.rs:7             (Some(i), Some(nxt_i)) => println!("{} {}", i.ok(), nxt_i.ok()),
                                                                               ^~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
/tmp/main.rs:7:39: 7:77 note: expansion site
error: aborting due to previous error

I fail to understand what rust is saying to me here, how Iterator.next would have a different return type from Peekable.peek.

3

3 Answers

9
votes

Peekable is not a trait and thus cannot be used as a bound, which would suggest that it could mean one of many types. It is a single, specific, concrete type, struct Peekable<A, T>. As you have observed, it’s constructed by calling the peekable() method on an iterator, which changes it to something that is peekable.

Here’s how you’d use it if you just wanted to take an iterator:

fn trawl<I, E>(iter: I) where I: Iterator<Result<char, E>> {
    let pk = pk.peekable();
    …
}

Note also that the peekable() method takes self by value; you can’t take a mutable reference to an iterator there.

The alternative which is what you were aiming for but which I would be generally less inclined towards, would be to require the argument to be peekable, putting the burden onto the caller, as you had:

fn trawl<I, E>(pk: Peekable<E, I>) where I: Iterator<Result<char, E>> {
    …
}
6
votes

Peekable is actually a struct, not a trait. If you wanted to take a Peekable, you could define your function like this:

fn trawl<E, I>(it: Peekable<I>) where I: Iterator<Result<char, E>> {
    ...
}

Your second implementation is failing to compile because peek takes self by value (i.e. it consumes the iterator, returning a new one), so you can't call it through a &mut reference. Most code simply takes the iterator by value instead of by reference:

fn trawl<E, I>(it: I) where I: Iterator<Result<char, E>> {
    let it = it.peekable();
    ...
}

If you don't want to move the iterator into a function like trawl, you can use the by_ref() method to create a new iterator that holds onto an &mut reference:

let mut my_iterator = /* whatever */;
trawl(my_iterator.by_ref());
// my_iterator is still usable here

As far as style goes, I would say that the second form is the better way to go, as the first leaks what's basically an implementation detail.

5
votes

Rust has changed a bit since the previous answers. The way to do it now is:

fn trawl<I, E>(pk: Peekable<I>) 
where I: Iterator<Item = Result<char, E>> {
    …
}