如何使用更高阶的特征范围使返回的impl Fn更通用?

时间:2019-12-04 02:33:31

标签: rust

我最近遇到了Rust的高级特征界线,并认为我可以使用它们在我编写的泛型解析器中实现某些功能。但是,我所做的修改使我收到一条错误消息,提示我无法做出正确的决定。

这是我现在可以使用的功能:

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

编译。但是,我的直觉告诉我,我从impl Fn返回的str_tokenstr_token上的生命周期参数的约束不一定很好。我相信,采用这种方式可能会不必要地限制返回的impl Fn特征的有用性。因此,我认为我可以对其进行修改,以返回一个impl Fn的生命周期'b,无论工厂函数str_token_hrtb的生命周期是什么:

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

现在,编译器给了我这些错误:

error[E0277]: expected a `std::ops::Fn<(&'b str,)>` closure, found `impl std::ops::Fn<(&str,)>`
  --> src/main.rs:29:6
   |
29 | ) -> impl for<'b> Fn(&'b str) -> IResult<&str, &str, E>
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `Fn<(&'b str,)>` closure, found `impl std::ops::Fn<(&str,)>`
   |
   = help: the trait `for<'b> std::ops::Fn<(&'b 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<'b> <impl std::ops::Fn<(&str,)> as std::ops::FnOnce<(&'b str,)>>::Output == std::result::Result<(&'b str, &'b str), nom::internal::Err<E>>`
  --> src/main.rs:29:6
   |
29 | ) -> impl for<'b> Fn(&'b str) -> IResult<&str, &str, E>
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter 'b, found concrete lifetime
   |
   = note: the return type of a function must have a statically known size

我不明白该怎么读。是否表示对于返回值for<'b> std::ops::...未实现特征verify?如果是这样,为什么不呢?为什么str_token不会存在相同的问题?另外,我找不到任何方法来解释第二条type mismatch错误消息。

任何人都可以对我在这里做错的事情以及编译器试图告诉我的事情给出一些见解吗?

更新

我正在使用在这里找到的nom解析库:https://github.com/Geal/nom/

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

另一个更新

决定关闭此步骤,因为我意识到我可能没有问过足够具体的问题。

1 个答案:

答案 0 :(得分:2)

错误消息确实不理想。这是因为尚未解决leak check step处理hrtb生命周期的所有困难。 Niko一直在it上工作。

如果您通过+nightly -Zno-leak-check标志关闭泄漏检查,则会收到更清晰的错误消息:

error: implementation of `std::ops::FnOnce` is not general enough
   --> src/main.rs:27:6
    |
27  |   ) -> impl for<'b> Fn(&'b str) -> IResult<&str, &str, E>
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `std::ops::FnOnce` is not general enough
    |
    = note: `impl std::ops::Fn<(&str,)>` must implement `std::ops::FnOnce<(&str,)>`
    = note: ...but `std::ops::FnOnce<(&'b str,)>` is actually implemented for the type `impl std::ops::Fn<(&str,)>`

现在,当您实际返回FnOnce时,为什么编译器会抱怨Fn?我在推测,但这应该是合理的。 FnOnceFn的超级特征,rustc表示代码甚至不能满足超级特征,也无法满足更严格的特征本身。

这终于有意义了。考虑以下事实:verify被定义为:

pub fn verify<I: Clone, O1, O2, E: ParseError<I>, F, G>(
    first: F, 
    second: G
) -> impl Fn(I) -> IResult<I, O1, E> where
    F: Fn(I) -> IResult<I, O1, E>,
    G: Fn(&O2) -> bool,
    O1: Borrow<O2>,
    O2: ?Sized,

要求它是:

impl for<'b> Fn(&'b str) -> IResult<&str, &str, E>
where
    E: ParseError<&'a str>,

您要问forall<'b> 'b = 'a根本不可能。


了解Rust的生命周期是非常有用的,并且几乎是强制性的。但是,在编写自己的代码时,最好尽可能地跳过它们。终生淘汰规则可以帮助您做到这一点。一个人肯定不会自愿达到hrtb的生命:D

例如,编写one_tokenstr_token函数的更惯用的方式可能是:

fn one_token(input: &str) -> IResult<&str, &str>
{
    let res = terminated(is_not(" \t\r\n"), multispace0)(input)?;

    Ok(res)
}

fn str_token(input: &str, expected_string: String) -> IResult<&str, &str>
{
    let res = verify(one_token, |actual_string| {
        actual_string == expected_string
    })(input)?;

    Ok(res)
}