In trying to learn Rust a bit more, I'm working through Advent of Code 2020.
On Day 2, there's a simple parsing task for reading in a text file with lines formatted a particular way, and then doing some operations on each line. Instead of hand-writing yet another bad parser, I figured I'd try to learn the ecosystem a bit more and gave Nom a try.
Since the parsing is within the file-reading code, I figured it'd make sense for it to return std::io:Error like the file-reading code that calls this method
This code works:
use nom::character::complete::{alpha1, anychar, char, u64, i64, multispace0, multispace1};
use nom::sequence::tuple;
// Parse "<number>-<number> <char>: <string>"
fn parse_password_line(i: &str) -> Result<(u64, u64, char, &str), io::Error> {
let parse = tuple::<&str, _, (&str, nom::error::ErrorKind), _>((
u64,
char('-'),
u64,
multispace1,
anychar,
char(':'),
multispace0,
alpha1,
))(i);
match parse {
Ok(parse) => {
let parts = parse.1;
Ok((parts.0, parts.2, parts.4, parts.7))
}
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
}
}
But there are two things I can't figure out. First, having to include the generic info for tuple
; changing it to just:
let parse = tuple(( ...
gives:
error[E0283]: type annotations needed for `Result<(&str, (u64, char, u64, &str, char, char, &str, &str)), nom::Err<Error>>`
--> src/day02.rs:55:17
|
55 | let parse = tuple((
| ----- ^^^^^ cannot infer type for type parameter `E` declared on the function `tuple`
| |
| consider giving `parse` the explicit type `Result<(&str, (u64, char, u64, &str, char, char, &str, &str)), nom::Err<Error>>`, where the type parameter `E` is specified
|
= note: cannot satisfy `_: ParseError<&str>`
note: required by a bound in `tuple`
--> /Users/quantumet/.cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.0/src/sequence/mod.rs:266:23
|
266 | pub fn tuple<I, O, E: ParseError<I>, List: Tuple<I, O, E>>(
| ^^^^^^^^^^^^^ required by this bound in `tuple`
help: consider specifying the type arguments in the function call
|
55 | let parse = tuple::<I, O, E, List>((
| +++++++++++++++++
and it's very non-obvious to me from the Nom docs on how to tell what E
should be - it should implement ParseError
, and reading its docs shows there's an impl for (I, ErrorKind)
so I stuck that in and it seems to work. But why did I need to specify it, and what other choices were there? The Nom docs on tuple don't specify this, I presume because one of the assert_eq!
enables complete type inference there.
And perhaps the choice there leads to the second problem. I was trying to chain the error causes by passing in e
to the io::Error::new
method by using just:
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
but that gives:
error[E0621]: explicit lifetime required in the type of `i`
--> src/day02.rs:70:23
|
70 | Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
| ^^^^^^^^^^^^^^ lifetime `'static` required
And it's really not at all clear to me where 'static
could even be applied in these 6 lines of code! Which makes me suspect adding 'static
isn't actually the right thing to do, but I'm not sure what would be.
Forcing the Nom error to a string fixes that error, but that feels like I'm working around something I should actually understand better, and probably reduces the amount of debug info I could use higher up in the program (if this was a large program, instead of an exercise).
So I guess the general question is, how do you navigate the API of a large crate like Nom to understand what to plug into generic parameters like these, when the types are clearly interrelated, and the compiler errors don't help much?