2

I'm using cssparser crate to parse some CSS code. I would like to create a closure that is able to parse a type function. As a first step, I create a very simple code that way:

use cssparser::{Parser, ParserInput};

fn main() {
    let input_string = "type(\"image/png\")";
    let mut parser_input = ParserInput::new(input_string);
    let mut parser = Parser::new(&mut parser_input);

    let parse_type = |p: &mut Parser| {
        p.expect_function_matching("type")?;
        Ok("OK")
    };

    let res = parse_type(&mut parser);
}

I got the following error:

error[E0282]: type annotations needed for the closure `fn(&mut Parser<'_, '_>) -> std::result::Result<&str, _>`
 --> src/main.rs:9:43
  |
9 |         p.expect_function_matching("type")?;
  |                                           ^ cannot infer type of error for `?` operator
  |
  = note: `?` implicitly converts the error value into a type implementing `From<BasicParseError<'_>>`

As read in this answer, I added the return type of my closure:

    let parse_type = |p: &mut Parser| -> Result<&str, cssparser::BasicParseError> {
        p.expect_function_matching("type")?;
        Ok("OK")
    };

I still have an error I don't understand:

error: lifetime may not live long enough
 --> src/main.rs:9:9
  |
8 |     let parse_type = |p: &mut Parser| -> Result<&str, cssparser::BasicParseError> {
  |                       -                  ---------------------------------------- return type of closure is std::result::Result<&str, BasicParseError<'2>>
  |                       |
  |                       has type `&mut Parser<'1, '_>`
9 |         p.expect_function_matching("type")?;
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`

Apparently '1 is release before '2. How can it be the case as the parameter of my closure is a reference? It should still be alive after the call to my closure, right?

I tried to explicitly annotate the lifetime of my objects but I wasn't able to find the correct way to do it; I always have an "undeclared lifetime" error. For instance:

    let parse_type = |p: &mut Parser<'i, '_>| -> Result<&str, cssparser::BasicParseError<'i>> {
        p.expect_function_matching("type")?;
        Ok("OK")
    };
Pierre
  • 1,942
  • 3
  • 23
  • 43

2 Answers2

3

Unfortunately, closures can't declare lifetime arguments, which will be required to communicate the proper lifetimes for this function. Moving it out to a function gives a better error:

use cssparser::{Parser, ParserInput};

fn parse_type(p: &mut Parser) -> Result<&str, cssparser::BasicParseError> {
    p.expect_function_matching("type")?;
    Ok("OK")
}

fn main() {
    let input_string = "type(\"image/png\")";
    let mut parser_input = ParserInput::new(input_string);
    let mut parser = Parser::new(&mut parser_input);

    let res = parse_type(&mut parser);
}
error[E0106]: missing lifetime specifier
 --> src\main.rs:3:41
  |
3 | fn parse_type(p: &mut Parser) -> Result<&str, cssparser::BasicParseError> {
  |                  -----------            ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say which one of `p`'s 3 lifetimes it is borrowed from
help: consider introducing a named lifetime parameter
  |
3 | fn parse_type<'a>(p: &'a mut Parser) -> Result<&'a str, cssparser::BasicParseError> {
  |              ^^^^    ^^^^^^^^^^^^^^            ^^^

error[E0106]: missing lifetime specifier
 --> src\main.rs:3:58
  |
3 | fn parse_type(p: &mut Parser) -> Result<&str, cssparser::BasicParseError> {
  |                  -----------                             ^^^^^^^^^^^^^^^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say which one of `p`'s 3 lifetimes it is borrowed from
help: consider introducing a named lifetime parameter
  |
3 | fn parse_type<'a>(p: &'a mut Parser) -> Result<&str, cssparser::BasicParseError<'a>> {
  |              ^^^^    ^^^^^^^^^^^^^^                             ^^^^^^^^^^^^^^^^^^^

The compiler will try to deduce lifetimes automatically as best it can, but p has three lifetimes &'1 Parser<'2, '3> involved, so it doesn't know what lifetime &'_ str or BasicParseError<'_> should be deduced to.

Looking at the signature for Parser::expect_function_matching, you probably want:

fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result<&'i str, cssparser::BasicParseError<'i>> {
    p.expect_function_matching("type")?;
    Ok("OK")
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
2

Of course, while everything @kmdreko has said is absolutely correct, replacing your closure with a function will only work if you're not capturing any environment—and if that's the case, you could simply declare that parse_type is a function pointer with the relevant type and Rust will coerce the closure accordingly:

let parse_type: for<'i> fn(&mut Parser<'i, '_>) -> Result<_, cssparser::BasicParseError<'i>> = |p| {
    p.expect_function_matching("type")?;
    Ok("OK")
};

If however your closure does capture environment, then I guess the extension of @kmdreko's approach would be to define a struct to hold that environment and then have some implementation on that struct that defines an appropriate method. But that's a lot of grunt. You could instead make parse_type a trait object for one of the Fn traits and use an analog of the above approach:

let parse_type: &dyn for<'i> Fn(&mut Parser<'i, '_>) -> Result<_, cssparser::BasicParseError<'i>> = &|p| {
    p.expect_function_matching("type")?;
    Ok("OK")
};

In theory this will add an additional layer of indirection when invoking the closure, but I think in practice the optimiser will remove it (unless you pass the closure between functions, at least). And it'd be a pretty irrelevant expense in most cases anyway.

Finally, if your input_string actually comes from a function argument, then you'll be able to name its lifetime in the function signature and use that directly in your closure definition:

fn example<'i>(input_string: &'i str) {
    let mut parser_input = ParserInput::new(input_string);
    let mut parser = Parser::new(&mut parser_input);
      
    let parse_type = |p: &mut Parser<'i, '_>| -> Result<_, cssparser::BasicParseError<'i>> {
        p.expect_function_matching("type")?;
        Ok("OK")
    };
      
    let res = parse_type(&mut parser);
}
eggyal
  • 122,705
  • 18
  • 212
  • 237