返回`impl Fn`

时间:2019-12-04 19:42:38

标签: rust

我一直在尝试了解一些与我编写的返回impl Fn的函数有关的生命周期冲突。让我们从头开始。我有以下无法编译的代码文件:

use nom::bytes::complete::is_not;
use nom::character::complete::multispace0;
use nom::combinator::verify;
use nom::error::{
    ParseError,
    VerboseError,
};
use nom::sequence::terminated;
use nom::IResult;

fn one_token<'a, E>(input: &'a str) -> IResult<&str, &str, E>
where
    E: ParseError<&'a str>,
{
    terminated(is_not(" \t\r\n"), multispace0)(input)
}

fn str_token<'a, E>(expected_string: String) -> impl Fn(&'a str) -> IResult<&str, &str, E>
where
    E: ParseError<&'a str>,
{
    verify(one_token, move |actual_string| {
        actual_string == expected_string
    })
}

fn main() {
    let parser_1 = str_token::<VerboseError<_>>("foo".into());
    let string = "foo bar".to_string();
    let input = &string[..];
    let parser_2 = str_token::<VerboseError<_>>("foo".into());

    println!("{:?} {:?}", parser_1(input), parser_2(input),);
}

我收到此错误消息:

error[E0597]: `string` does not live long enough
  --> src/main.rs:30:18
   |
30 |     let input = &string[..];
   |                  ^^^^^^ borrowed value does not live long enough
...
34 | }
   | -
   | |
   | `string` dropped here while still borrowed
   | borrow might be used here, when `parser_1` is dropped and runs the destructor for type `impl std::ops::Fn<(&str,)>`
   |
   = note: values in a scope are dropped in the opposite order they are defined

看来,返回的impl Fn分配给parser_1的值仅适用于寿命至少与parser_1变量一样长的值。这违反了我的预期,parser_1可以与任何生存期的变量一起使用。最初,我怀疑这可能是由于'a上的生存期参数str_token和错误类型参数E之间的某种相互作用所致。所以我只是将错误类型明确了:

fn one_token(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
    terminated(is_not(" \t\r\n"), multispace0)(input)
}

fn str_token<'a>(
    expected_string: String,
) -> impl Fn(&'a str) -> IResult<&str, &str, VerboseError<&str>> {
    verify(one_token, move |actual_string| {
        actual_string == expected_string
    })
}

这不能解决问题。它会导致完全相同的编译错误。因此,我尝试修改str_token以使用更高等级的特征范围:

fn str_token(
    expected_string: String,
) -> impl for<'a> Fn(&'a str) -> IResult<&str, &str, VerboseError<&str>> {
    verify(one_token, move |actual_string| {
        actual_string == expected_string
    })
}

但随后出现此错误:

error[E0277]: expected a `std::ops::Fn<(&'a str,)>` closure, found `impl std::ops::Fn<(&str,)>`
  --> src/main.rs:14:6
   |
14 | ) -> impl for<'a> Fn(&'a str) -> IResult<&str, &str, VerboseError<&str>> {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `Fn<(&'a str,)>` closure, found `impl std::ops::Fn<(&str,)>`
   |
   = help: the trait `for<'a> std::ops::Fn<(&'a str,)>` is not implemented for `impl std::ops::Fn<(&str,)>`
   = note: the return type of a function must have a statically known size

error[E0271]: type mismatch resolving `for<'a> <impl std::ops::Fn<(&str,)> as std::ops::FnOnce<(&'a str,)>>::Output == std::result::Result<(&'a str, &'a str), nom::internal::Err<nom::error::VerboseError<&'a s
tr>>>`
  --> src/main.rs:14:6
   |
14 | ) -> impl for<'a> Fn(&'a str) -> IResult<&str, &str, VerboseError<&str>> {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter 'a, found concrete lifetime
   |
   = note: the return type of a function must have a statically known size

坦率地说,我基本上不知道该如何解释。有人可以评论这里发生的事情吗?为什么返回的impl Fn的生存期与生成它的工厂函数的生存期绑定在一起,即使其行为实际上并不取决于该生存期呢?如何解决此问题并仍然使用impl Fn返回值?当HRTB看起来像是完美的应用时,为什么不起作用?我在这里很迷路。

顺便说一句,我使用的是在这里找到的nom解析库:https://github.com/Geal/nom/

此外,verify函数的代码在这里:https://github.com/Geal/nom/blob/851706460a9311f7bbae8e9b7ee497c7188df0a3/src/combinator/mod.rs#L459

如果有人想玩一个包含所有这些示例的货运项目,这里有一个:https://github.com/davesque/nom-test/

您可以克隆它,检出first-versionno-error-parameterhigher-rank-trait-bounds标签,然后调用cargo run

注意

我最近在这里问了一个类似的问题:How to use higher-rank trait bounds to make a returned impl Fn more generic?

但是,我最终认为这个问题对我实际想做的事情还不够具体。有人已经回答了,所以我不想做大的修改并使答案变得混乱,而且显然与我的问题无关。

1 个答案:

答案 0 :(得分:2)

当您的函数/结构/特征的寿命为<'a>时,表示任何标记为'a的引用都必须超过函数/结构/特征。异常意味着(在其他事物中)被引用的事物必须在调用函数/创建结构/创建实现特性的项目之前已经存在。该引用不能稍后创建,因为这意味着它的生存期比要求的要晚。

在您的情况下,str_token<'a>表示用&'a str标记的字符串必须已经创建并且在调用str_token函数之前已经存在。

您的代码违反了您的要求:

 let parser_1 = str_token::<VerboseError<_>>("foo".into());
 let input = &string[..];

因为parser_1是在input之前创建的,但是其有效期注释允许它仅与解析器之前的 craped 字符串一起使用。

如果您交换这些行的顺序,它应该可以工作。

for<'b> impl Fn(&'b str)会更灵活,因为这意味着使用此功能的对象都可以“即时”定义生存期,因此任何生存期都可以使用。但是,您正在使用的库显然需要较不灵活的方法,这也许是出于与您的使用没有直接关系的充分原因。

这是一个最小的测试用例:

fn parser<'a>() -> impl Fn(&'a str) -> &str {
    |a| a
}

fn main() {
    let s1 = String::new();
    let p = parser();
    let s2 = String::new();
    p(&s1);
    //p(&s2);
}