Type issues with closures, traits, and where clauses


#1

This is a follow-on to [Solved] Trouble with Peekable and SkipWhile structs

I’m now trying to refactor the code in that post like so:

use std::iter::{Iterator, Peekable, SkipWhile};
use std::ops::Fn;
use std::str::Chars;
use std::string::String;


struct Parser<'a, I: 'a>
where
    I: Iterator<Item=char>
{
    chars: &'a I,
}

impl<'a, I, P> Parser<'a, Peekable<SkipWhile<I, P>>>
where
    I: Iterator<Item=char>,
    P: Fn(&char) -> bool
{
    fn from_string(string: &String) -> Parser<Peekable<SkipWhile<Chars, P>>> {
        Parser {
            chars: &string
                .chars()
                .skip_while(|c| match *c {
                    '"' => true,
                    _ => false,
                })
                .peekable()
        }
    }

    fn collect_and_unescape(&mut self, value: &mut String) {
        loop {
            let next = self.chars.next();
            let next_char = match next {
                Some('\\') => {
                    match self.chars.peek() {
                        Some(&'"') | Some(&'\\') => self.chars.next(),
                        _ => None,
                    }
                }
                Some('"') | None => break,
                _ => next,
            };
            if let Some(c) = next_char {
                value.push(c);
            }
        }
    }

    fn skip_chars_while<C>(&mut self, predicate: C)
    where
        C: Fn(Option<&char>) -> bool
    {
        loop {
            if predicate(self.chars.peek()) {
                self.chars.next();
            } else {
                break;
            }
        }
    }
}

impl<'a, I, P> Iterator for Parser<'a, Peekable<SkipWhile<I, P>>>
where
    I: Iterator<Item=char>,
    P: Fn(&char) -> bool
{
    type Item = (String, String);

    fn next(&mut self) -> Option<Self::Item> {
        if self.chars.peek() == None {
            return None;
        }
        let mut key = String::new();
        let mut value = String::new();
        self.collect_and_unescape(&mut key);
        self.skip_chars_while(|c| match c {
            Some(&'"') => false,
            _ => true,
        });
        Some((key, value))
    }
}

fn main() {
    let s = String::from("\"hell\\\"o_world\"=>\"value\\\" with quotes\"");
    let mut chars = s.chars()
        .skip_while(|c| match *c {
            '"' => true,
            _ => false,
        })
        .peekable();
    let mut key = String::new();
    let mut value = String::new();
    //collect_and_unescape(&mut key, &mut chars);
    let mut chars = chars.skip_while(|c| match *c {
        '"' => false,
        _ => true,
    }).peekable();
    chars.next();
    //collect_and_unescape(&mut value, &mut chars);
    //match chars.peek() {
    //    Some(&',') | Some(' ') =>
    //}

    println!("key: {}, value: {}", key, value);
    let mut chars = chars.skip_while(|c| match *c {
        '"' => false,
        _ => true,
    }).peekable();
}

But when I compile it I get mismatched types because SkipWhile is expecting P and getting a closure:

❯❯❯ rustc test.rs
error[E0308]: mismatched types
  --> test.rs:20:9
   |
20 | /         Parser {
21 | |             chars: &string
22 | |                 .chars()
23 | |                 .skip_while(|c| match *c {
...  |
27 | |                 .peekable()
28 | |         }
   | |_________^ expected type parameter, found closure
   |
   = note: expected type `Parser<'_, std::iter::Peekable<std::iter::SkipWhile<std::str::Chars<'_>, P>>>`
              found type `Parser<'_, std::iter::Peekable<std::iter::SkipWhile<std::str::Chars<'_>, [closure@test.rs:23:29: 26:18]>>>`

error: aborting due to previous error(s)

I’m at a loss for how to resolve this. I think I’m doing the correct thing, but then again, I was also apparently misunderstanding how to impl my Parser since I thought the iterator trait would be good enough instead of having to specify Peekable<SkipWhile<std::str::Chars, ...>>.

Help and advice on better ways to do this is greatly appreciated.


#2

I think I’ve followed the help of the compiler too closely. Even if I extract the closure to a function that has the signature of the trait, it still doesn’t work.

What I want to end up with is something that I can do (vaguely):

let mut parser = Parser::from_string(String::from("\"hell\\\"o_world\"=>\"value\\\" with quotes\""));
let (key, value) = parser.next();
// or
let items = parser.collect::Vec<Vec<String>>();

But the compiler has led me down this rabbit hole of specifying Peekable<SkipWhile<...>> and it’s not clear to me how if there’s a better way here.


#3

You have

fn from_string(string: &String)
    -> Parser<Peekable<SkipWhile<Chars, P>>> {

That won’t work - you’re saying you’re returning a SkipWhile with a closure of type P chosen by the caller.

This is where you need to use impl Trait, or in your case, you can also replace the P in the return type with fn(&char) -> bool (as it’s not capturing anything so it can become a function pointer just file).


#4

Hm, I have this now and it’s still giving me an type mismatch error:

fn from_string(string: &String)
    -> Parser<Peekable<SkipWhile<Chars, fn(&char) -> bool>>>
{
    Parser {
        chars: &string
            .chars()
            .skip_while(|c| match *c {
                '"' => true,
                _ => false,
            })
            .peekable()
    }
}

And I’m getting as similar message that it’s expecting a type with fn(&char) -> bool and received &[closure...]. If I pull the closure into its own function, e.g.,

fn skip_while_dquote(c: &char) -> bool {
    match *c {
        '"' => true,
        _ => false,
    }
}

I get

expected fn pointer, found fn item

Which I got rid of which an explicit cast, but that brings up a whole slew of errors that make me think following the compiler’s advice (which has brought me to generics and lifetimes) as a bad idea. Time to start over from scratch.

Thanks for the guidance in the right direction @eddyb.


#5

Ah yeah you need an explicit cast, but it should work with a closure too.


#6

You have a sort of hybrid approach going on with some generics but also concrete types wrapped in the Parser. Is that intentional? You’ll have an easier time if you stick entirely with generics, Iterator in this case. Peekable and SkipWhile are still Iterator types, so you can “hide” them by sticking to Iterator only.