I'm trying to implement a parser combinator in rust.
I have the following parser, which is built with no errors/warnings:
use std::char;
#[derive(Debug)]
enum ParseResult<T> {
Success(T),
Failure(&'static str),
}
fn pchar(char_to_match: char) -> impl Fn(&str) -> ParseResult<(&str, &str)> {
move |string: &str| match string.get(0..1) {
Some(found) => match found.chars().next().unwrap() == char_to_match {
true => ParseResult::Success((found, string.get(1..).unwrap())),
false => ParseResult::Failure("char didnt match"),
},
None => ParseResult::Failure("No more left to parse."),
}
}
fn main() {
println!("{:?}", ParseResult::Success(&3));
println!("{:?}", pchar('s')("r"));
println!("{:?}", (|(a, b)| (a, b))((2, 3)));
let input_abc = "ABC";
println!("{:?}", pchar('A')(input_abc));
Instead of returning a function (closure) from pchar, I want to return a Parser<T> which I have declared as:
union Parser<T> {
func: dyn Fn(&str) -> ParseResult<(T, &str)>,
}
Then my second iteration of pchar can return a Parser<T> such as:
fn pchar2(char_to_match: char) -> Parser<&str> {
Parser {
func: |string: &str| match string.get(0..1) {
Some(found) => match found.chars().next().unwrap() == char_to_match {
true => ParseResult::Success((found, string.get(1..).unwrap())),
false => ParseResult::Failure("char didnt match"),
},
None => ParseResult::Failure("No more left to parse."),
},
}
}
I get all sorts of warnings regarding lifetime parameters, size at compile-time and expected trait dyn Fn.
What do I need to learn to solve this issue?
Update:
As per Aleksander Krauze's suggestion, I have updated it to use a Box.
struct Parser<T> {
func: Box<dyn Fn(&str) -> ParseResult<(T, &str)>>,
}
fn pchar2(char_to_match: char) -> Parser<&'static str> {
Parser {
func: Box::new(move |string: &str| match string.get(0..1) {
Some(found) => match found.chars().next().unwrap() == char_to_match {
true => ParseResult::Success((found, string.get(1..).unwrap())),
false => ParseResult::Failure("char didnt match"),
},
None => ParseResult::Failure("No more left to parse."),
}),
}
}
The error I get is that the lifetime of the variable string must outlive char_to_match.
Update 2: As per Aleksander Krauze's answer. Here is the final working version:
fn pchar2<'a>(char_to_match: char) -> Parser<'a, &'a str> {
Parser {
func: Box::new(move |string: &'a str| match string.char_indices().next() {
Some((i, c)) => match c == char_to_match {
true => ParseResult::Success((
string.get(..i + 1).unwrap(),
string.get(i + 1..).unwrap(),
)),
false => ParseResult::Failure("char didnt match"),
},
None => ParseResult::Failure("No more left to parse."),
}),
}
}
Following snippet should fix your lifetime errors. The main problem was that you tried to return
&'static str, when it was only a subslice ofstring. Types here are a little complex, but you could try to simplify them ifParserwouldn't be generic overT(but haveT = &'a str). I don't know however what is your more general usecase, so it might not be viable.Note that even that this code compiles, it has a bug! In rust strings are UTF-8 encoded, so single character can take anywhere from 1 to 4 bytes. But when you slice
&stryou are giving bytes indexes. That could result in a panic at runtime if you would slice in the middle of a character.