Home > database >  Why tuple needs type annotation in nom library?
Why tuple needs type annotation in nom library?

Time:08-22

I'm writing a parser using nom library in rust. When using tuple I encounter a problem. This snippet works fine:

use std::str;

use nom::bytes::complete::take_while1;
use nom::bytes::complete::{tag, take_while};
use nom::character::complete::{char, multispace0};
use nom::sequence::tuple;
use nom::IResult;


fn identifier(input: &str) -> IResult<&str, &str> {
  take_while1(is_ascii_alphanumeric)(input)
}

fn parameter_separator(input: &str) -> IResult<&str, &str> {
  let my_parser = tuple((multispace0, char(','), identifier));
  let (input, _) = multispace0(input)?;
  let (input, _) = char(',')(input)?;
  let (input, _) = multispace0(input)?;

  Ok((input, ""))
}

But when replacing the identifier parser with multispace0 parser the compiler ask me to type annotate the my_parser.


fn parameter_separator(input: &str) -> IResult<&str, &str> {
  let parser = tuple((multispace0, char(','), multispace0));
  let (input, _) = multispace0(input)?;
  let (input, _) = char(',')(input)?;
  let (input, _) = multispace0(input)?;

  Ok((input, ""))
}
49  |   let parser = tuple((multispace0, char(','), multispace0));
    |       ------   ^^^^^ cannot infer type for type parameter `E` declared on the function `tuple`
    |       |
    |       consider giving `parser` a type

What is the difference? Why the second raises error?

CodePudding user response:

tuple has a generic parameter E which must be an instance of ParserError<I>.

In your second function, the compiler cannot infer the type of tuple's E parameter from the context. In tuple((multispace0, char(','), multispace0))(input), none of the arguments passed to tuple gives sufficient information of what E might be. Therefore, you are seeing the following error:

pub fn tuple<I, O, E: ParseError<I>, List: Tuple<I, O, E>>(
                      ^^^^^^^^^^^^^ required by this bound in `tuple`
  

Your first example worked because identifier has the type IResult<&str, &str> which itself expands to Result<(&str, &str), nom::Err<nom::error::Error<&str>>>.

This allows the compiler to instantiate E to nom::Err<nom::error::Error<&str>> in the tuple() call.

Thus, if you define an alias for multispace0 that does not leave E unspecified, the code would compile correctly:

fn narrowed_multispace0(input: &str) -> Result<(&str, &str), nom::Err<nom::error::Error<&str>>> {
  multispace0(input)
}

let _ = tuple((narrowed_multispace0, char(','), multispace0))(input);

Rust implements the Hindley-Milner type system which performs bidirectional type inference. This allows E to be inferred even from the function's result type as in the following example:

fn parameter_separator(input: &str) -> IResult<&str, &str> {
  map(  // To map (&str, &str, &str)` onto `&str`
    tuple((multispace0, char(','), multispace0)),
    |_| "test"
  )(input)
}
  • Related