为什么闭包的可变引用参数不会超过函数调用?

Why the mutable reference parameter of a closure doesn't outlive the function call?

我正在使用 cssparser crate 来解析一些 CSS 代码。我想创建一个能够解析 type 函数的闭包。作为第一步,我创建了一个非常简单的代码:

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);
}

我收到以下错误:

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<'_>>`

如本文 中所述,我添加了闭包的 return 类型:

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

我还有一个错误我不明白:

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`

显然 '1 是在 '2 之前发布的。我的闭包参数是引用怎么会这样呢?调用我的闭包后它应该还活着,对吧?

我试图明确注释对象的生命周期,但找不到正确的方法;我总是有一个“未声明的生命周期”错误。例如:

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

不幸的是,,这将需要传达此函数的正确生命周期。将它移出一个函数会给出更好的错误:

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>> {
  |              ^^^^    ^^^^^^^^^^^^^^                             ^^^^^^^^^^^^^^^^^^^

编译器会尽量自动推导生命周期,但是 p 涉及三个生命周期 &'1 Parser<'2, '3>,所以它不知道生命周期 &'_ strBasicParseError<'_> 应推导为.

查看 Parser::expect_function_matching 的签名,您可能想要:

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

当然,虽然@kmdreko 所说的一切都是绝对正确的,但只有当您没有捕获任何环境时,用函数替换您的闭包才会起作用——如果是这种情况,您可以简单地声明 parse_type 是具有相关类型的函数指针,Rust 将相应地强制关闭:

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

如果您的闭包 确实 捕获环境,那么我猜@kmdreko 方法的扩展将是定义一个结构来保存该环境,然后在该结构上进行一些实现定义了一个合适的方法。但这是很多咕噜声。您可以改为使 parse_type 成为 Fn 特征之一的特征对象,并使用上述方法的模拟:

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

理论上这会在调用闭包时添加一个额外的间接层,但我认为实际上优化器会删除它(除非您至少在函数之间传递闭包)。无论如何,在大多数情况下,这将是一笔无关紧要的费用。

最后,如果您的 input_string 实际上来自一个函数参数,那么您将能够在函数签名中命名它的生命周期并直接在您的闭包定义中使用它:

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);
}