Reputation: 128041
Suppose I want to create a combinator which uses another parser multiple times, for example, to parse a string delimited by two kinds of quotes:
fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
map(
alt((
tuple((tag("'"), f, tag("'"))),
tuple((tag("\""), f, tag("\"")))
)),
|(_, res, _)| res,
)
}
This parser, as expected, fails to compile with the "use of moved value" error:
149 | fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
| - - move occurs because `f` has type `F`, which does not implement the `Copy` trait
| |
| consider adding a `Copy` constraint to this type argument
...
155 | tuple((tag("'"), f, tag("'"))),
| - value moved here
156 | tuple((tag("\""), f, tag("\"")))
| ^ value used here after move
However, I can't just add Copy
or Clone
to the F
bounds: a lot of parsers, in particular, returned by Nom's builtin functions, do not implement neither Clone
nor Copy
. I can't also use &f
as an argument to tuple
, because then it would be a borrow checking error (f
is a temporary local value, so it is not possible to return a parser constructed with it).
The only way I see doing this is actually reimplementing the alt
logic directly in the function, by unrolling it in a sequence of nested match
statements, but this seems really suboptimal. Or maybe I'm missing something simple, and it is actually possible to do what I want using combinators only?
I'm pretty sure that there is a better way of writing specifically the quoted
combinator as described above, and it would be nice if someone shows it, but my question is more general - how do I write combinators which reuse the same parser?
Upvotes: 0
Views: 1288
Reputation: 128041
The simplest way would be to be explicit with the returned closure:
fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
move |i| {
map(
alt((
tuple((tag("'"), &f, tag("'"))),
tuple((tag("\""), &f, tag("\"")))
)),
|(_, res, _)| res,
)(i)
}
}
Now, because of the move
keyword, the f
value is moved into the closure. Then, inside the returned closure I invoke the complex parser combinator directly, and nothing except output/error is returned from the closure, which means I'm free to use references to f
.
Upvotes: 5