Mysterious error "one type is more general than the other" when using Box with nom parsers

771 views Asked by At

While trying to solve today's Advent of Code puzzle, I am attempting to use nom to build some dynamic parsers depending on input strings. But in doing so I've come against what to me is a very peculiar problem, which my lack of familiarity to Rust (I am using the Advent of Code to try to learn Rust, having never used it, or any other "low level" language, before) is making impossible for me to solve.

The following is an ultra-simplified example which, if it compiled, would obviously not do anything useful - but it illustrates the compiler error (sadly I can't put it on the Rust Playground as that doesn't have nom available, but it can be easily run independently):

use nom::IResult;

type MyParser = Box<dyn Fn(&str) -> IResult<&str, ()>>;

fn build_parser() -> MyParser {
    let parser = |_| Ok(("", ()));
    Box::new(parser)
}

fn main() {
    let parser = build_parser();
    let result = parser("some dummy text");
    println!("result was {:?}", result);
}

The Box is needed otherwise the compiler complains about not knowing the size of the type at compile-time.

This fails with 2 very similar errors on the Box::new line (although note, in my real code only the second of these occurs), both complaining of mismatched types which are in fact identical:

mismatched types

one type is more general than the other

note: expected enum `std::result::Result<(&str, ()), nom::Err<nom::error::Error<&str>>>`
         found enum `std::result::Result<(&str, ()), nom::Err<nom::error::Error<&str>>>`

mismatched types

one type is more general than the other

note: expected type `std::ops::FnOnce<(&str,)>`
         found type `std::ops::FnOnce<(&str,)>`

Note: I have googled and came across many questions about similar errors - of which this seemed the most promising, but nothing in that answer, or other discussions I found, gave me any clues as to what might be going on in my case. I have tried quite a few changes, but nothing so far has worked, and I feel like I'm just floundering with no understanding of what causes this error.

Any insight would be much appreciated, that could fix the above without changing it drastically.

1

There are 1 answers

3
vallentin On

Given lifetime elision, then your MyParser Fn is interpreted as:

Fn(&'a str) -> IResult<&'a str, ()>

However, the compiler interprets the return type of your closure as &'static str:

Fn(&str) -> IResult<&'static str, ()>

Thereby resulting in the "mismatch types" error.


You can resolve this issue by explicitly defining the lifetime.

type MyParser<'a> = Box<dyn Fn(&'a str) -> IResult<&'a str, ()>>;

fn build_parser<'a>() -> MyParser<'a> {
    let parser = |_| Ok(("", ()));
    Box::new(parser)
}

Also, you don't need to use Box you can use impl Fn.

fn build_parser<'a>() -> impl Fn(&'a str) -> IResult<&'a str, ()> {
    let parser = |_| Ok(("", ()));
    parser
}

This is related to issue (22340) "Can’t declare lifetime for closure that returns a reference" and potentially issue (71723) for the confusing error.