rust-bakery / nom

Rust parser combinator framework
MIT License
9.18k stars 792 forks source link

`map` errors on applying closure, which returns closure #1682

Open d1agnozzz opened 10 months ago

d1agnozzz commented 10 months ago

Hey there! I needed to return parse result as a boxed closure Fn(usize) -> usize. Like every time before, I used map on a parser for this purpose, in which i pass a closure, that returns boxed closure. But compiler keeps cursing me with

1. mismatched types
   expected enum `std::result::Result<(&str, std::boxed::Box<(dyn std::ops::Fn(usize) -> usize + 'static)>), nom::Err<nom::error::Error<&str>>>`
      found enum `std::result::Result<(&str, std::boxed::Box<[closure@src/test.rs:19:18: 19:26]>), nom::Err<_>>` [E0308]

I researched many sources on how to return closures in rust, and what I'm trying to do should theoretically work, but it doesn't.

And what is interesting, if I destruct parser Result myself and then return Ok variant with closure — it works! So I concluded there should be something about map combinator

Prerequisites

Test case

It's not actually a unit test, it's just two functions: first function throws compilation error, second passes.

use nom::{
    bytes::complete::tag,
    character::complete::{multispace0, one_of},
    combinator::map,
    sequence::separated_pair,
    IResult,
};

fn parse_operation_error(i: &str) -> IResult<&str, Box<dyn Fn(usize) -> usize>> {
    let parsed = separated_pair(one_of("+*"), multispace0, tag("old"));

    map(parsed, |(operator, _)| {
        let oparation = match operator {
            '+' => usize::saturating_add,
            '*' => usize::saturating_mul,
            _ => panic!(),
        };

        Box::new(move |x| oparation(x, x)) // <- Returning closure in map's closure; DOESN'T WORK
    })(i)
}

fn parse_operation_no_error(i: &str) -> IResult<&str, Box<dyn Fn(usize) -> usize>> {
    let parsed: IResult<&str, (char, &str)> =
        separated_pair(one_of("+*"), multispace0, tag("old"))(i);
    if let Ok((remain, (operator, _))) = parsed {
        let operaion = match operator {
            '+' => usize::saturating_add,
            '*' => usize::saturating_mul,
            _ => panic!("Operator {operator} is not allowed"),
        };
        return Ok((remain, Box::new(move |x| operaion(x, x)))); // <- Returning Ok() variant with
                                                                // closure; works perfectly fine
    }
    panic!("Error parsing operation with old, input: {i}");
}

Compiler says:

error[E0308]: mismatched types
  --> src/test.rs:12:5
   |
9  |   fn parse_operation_error(i: &str) -> IResult<&str, Box<dyn Fn(usize) -> usize>> {
   |                                        ------------------------------------------ expected `Result<(&str, Box<(dyn Fn(usize) -> usize + 'static)>), nom::Err<nom::error::Error<&str>>>` because of return type
...
12 | /     map(parsed, |(operator, _)| {
13 | |         let oparation = match operator {
14 | |             '+' => usize::saturating_add,
15 | |             '*' => usize::saturating_mul,
...  |
19 | |         Box::new(move |x| oparation(x, x)) // <- Returning closure in map's closure
20 | |     })(i)
   | |_________^ expected `Result<(&str, Box<dyn Fn(usize) -> usize>), Err<Error<&str>>>`, found `Result<(&str, Box<[closure@test.rs:19:18]>), Err<_>>`
   |
   = note: expected** enum `Result<(&str, Box<(dyn Fn(usize) -> usize + 'static)>), nom::Err<nom::error::Error<&str>>>`
              found enum `Result<(&str, Box<[closure@src/test.rs:19:18: 19:26]>), nom::Err<_>>`
help: use `?` to coerce and return an appropriate `Err`, and wrap the resulting value in `Ok` so the expression remains of type `Result`
   |
12 ~     Ok(map(parsed, |(operator, _)| {
13 |         let oparation = match operator {
 ...
19 |         Box::new(move |x| oparation(x, x)) // <- Returning closure in map's closure
20 ~     })(i)?)
   |