Nom: create function that returns parser and some other questions

Lets for example create a parser for integers:

fn parse_i32(s: &str) -> IResult<&str, i32> {
    map_res(digit1, |s: &str| s.parse())(s)
}

That just works, but I see two issues here:

  • It cannot be used with different error or input types
  • It would be better if this function returned a parser instead of being a parser itself. It would be cleaner and may be more effective.

When I try to make it generic over input type, like this: parse_i32<I>(s: I) -> IResult<I, i32>, it starts making me add lots of constraints over type I, and I feel like there should be one or two constraint over this type. Also, I cannot use FromStr to parse i32, because the input type now is not &str.

When I try to make it return a parser without making it general over input type, and possibly making it general over error type, I get errors that I just do not understand:

fn parse_i32_2() -> impl FnMut(&str) -> IResult<&str, i32> {
    map_res(digit1, |s: &str| s.parse())
}

And the errors are:

error: implementation of `FnMut` is not general enough
  --> src/day13/input_parser.rs:17:5
   |
17 |     map_res(digit1, |s: &str| s.parse())
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnMut` is not general enough
   |
   = note: `impl FnMut(&'2 str) -> Result<(&'2 str, i32), nom::Err<nom::error::Error<&str>>>` must implement `FnMut<(&'1 str,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnMut<(&'2 str,)>`, for some specific lifetime `'2`

error[E0308]: mismatched types
   --> src/day13/input_parser.rs:17:5
    |
17  |     map_res(digit1, |s: &str| s.parse())
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
   ::: /Users/romamik/.cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/src/combinator/mod.rs:107:6
    |
107 | ) -> impl FnMut(I) -> IResult<I, O2, E>
    |      ----------------------------------
    |      |
    |      the expected opaque type
    |      the found opaque type
    |
    = note: expected associated type `<impl FnMut(&str) -> Result<(&str, i32), nom::Err<nom::error::Error<&str>>> as FnOnce<(&str,)>>::Output`
               found associated type `<impl FnMut(&str) -> Result<(&str, i32), nom::Err<nom::error::Error<&str>>> as FnOnce<(&str,)>>::Output`

I think these errors can be solved by adding named lifetimes, but making parser generic over I will also resolve them and I prefer making it generic.

What is the right way to make such a parser?

Check out how nom itself does it: nom/mod.rs at main · Geal/nom · GitHub

The main trick seems to be being generic over input which means you're not ambiguous over str lifetimes.

As I can see nom itself uses different constraints over I for different parsers and because of this I have to put all of the constraints from all the parsers I've used in my own parser. That feels just too much, I'd prefer one more universal constraint, even if it will be more restricting. And it may already exist, but I just didn't find it.

Another question is about using FromStr in map_res function in the original example: digit1 parser uses the same type for input and output, when this type is &str everything is clear and I can just use FromStr, but if I use generic I how would I find out the value of the parsed integer, or more generally how would I find the string that matched the parser to process it?

I'm trying to figure out how to parse a signed number, and this I guess ignores the sign?

@alper try this:

    fn parse_num(s: &str) -> IResult<&str, i32> {
        map_res(recognize(pair(opt(one_of("+-")), digit1)), |s: &str| {
            s.parse()
        })(s)
    }

I think the key is recognize here.

I'm still interested in my original question.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.