Search code examples
rustclosuresmovenom

How to reuse small parsers in `alt` when they cannot `move`?


As an example I want to parse a string that can either be 2 dot-separated words or a single word. Each "word" is a combination of alphanumeric and underscore characters, so I write the word-parser as a closure. In either case I want the parser-function to return a pair of words with one of them potentially being an empty string. There might be a better solution for this specific problem, but I simply want to motivate my problem here.

Writing my parser with nom I stumble upon the following problem: I cannot write my sub-parsers as closures (in order to re-use them), because the closures are moved if I use them multiple times in an alt statement:

fn parse_dot_sep_words_or_word(i: &str) -> IResult<&str, (&str, &str)> {
    let word = recognize(many0(alt((alphanumeric1, tag("_")))));
    let dot_sep_word = separated_pair(word, tag("."), word);

    alt((
        map(word, |single_word| ("", single_word)),
        map(dot_sep_word, |(a,b)| (a,b))
    ))
    (i)
}

This does not work as word gets moved once it is used inside dot_sep_word. Following the instructions of the compiler ("use &mut word") doesn't help either.

The only solution I can use is to write out what word is defined as everytime word is used. But this seems counterintuitive to the advantages nom offers.

What did I miss here? Is there a simple way this can be solved?


Solution

  • One option is to simply make word a function instead of a closure.

    fn word(i: &str) -> IResult<&str, &str> {
        recognize(many0(alt((alphanumeric1, tag("_")))))(i)
    }
    
    fn parse_dot_sep_words_or_word(i: &str) -> IResult<&str, (&str, &str)> {
        let dot_sep_word = separated_pair(word, tag("."), word);
    
        alt((
            map(word, |single_word| ("", single_word)),
            map(dot_sep_word, |(a, b)| (a, b)),
        ))(i)
    }