简体   繁体   English

如何使用更高等级的特征边界使返回的 impl Fn 更通用?

[英]How to use higher-rank trait bounds to make a returned impl Fn more generic?

I ran across Rust's higher-rank trait bounds recently and thought I could use them to make some functions in a parser I'm writing more generic.我最近遇到了 Rust 的高级特征边界,并认为我可以使用它们在我正在编写的解析器中创建一些更通用的函数。 However, a modification I've made is giving me an error message that I can't make heads or tails of.但是,我所做的修改给了我一条错误消息,我无法做出正面或反面。

Here's what I've got right now that works:这是我现在所拥有的有效方法:

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

This compiles.这编译。 However, my intuitions tell me that it's not necessarily great that the impl Fn I'm returning from str_token is bound by a lifetime parameter on str_token .然而,我的直觉告诉我,这并不一定伟大的impl Fn我从返回str_token由lifetime参数约束上str_token I believe having it that way could unnecessarily restrict the usefulness of the impl Fn trait that gets returned.我相信这样做可能会不必要地限制返回的impl Fn特征的有用性。 So I thought I could modify it to return an impl Fn that works for any lifetime 'b , regardless of what the lifetime of the factory function str_token_hrtb is:所以我想我可以修改它以返回一个适用于任何生命周期'bimpl Fn ,无论工厂函数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
    })
}

Now, the compiler is giving me these errors:现在,编译器给了我这些错误:

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

I don't understand how to read this.我不明白如何阅读这个。 Is it saying that the trait for<'b> std::ops::... is not implemented for the return value of verify ?是说for<'b> std::ops::...的特征没有为verify的返回值实现吗? If so, why not?如果是这样,为什么不呢? And why wouldn't the same issue exist for str_token ?为什么str_token不存在相同的问题? Also, I can't find any way to interpret the second type mismatch error message.另外,我找不到任何方法来解释第二种type mismatch错误消息。

Can anyone give some insight on what I'm doing wrong here and what the compiler is trying to tell me?任何人都可以对我在这里做错了什么以及编译器试图告诉我的内容有所了解吗?

Update :更新

I'm using the nom parsing library found here: https://github.com/Geal/nom/我正在使用此处找到的 nom 解析库: https : //github.com/Geal/nom/

Also, the code for the verify function is here: https://github.com/Geal/nom/blob/851706460a9311f7bbae8e9b7ee497c7188df0a3/src/combinator/mod.rs#L459此外, verify功能的代码在这里: https : //github.com/Geal/nom/blob/851706460a9311f7bbae8e9b7ee497c7188df0a3/src/combinator/mod.rs#L459

Another Update :另一个更新

Decided to close this since I realized I may not have asked questions that were specific enough.决定关闭这个,因为我意识到我可能没有问足够具体的问题。

The error message is indeed less than ideal.错误信息确实不太理想。 That's because not all difficulties of the leak check step of handling the hrtb lifetimes have been ironed out yet.这是因为并非所有处理 hrtb 寿命的泄漏检查步骤的困难都已解决。 Niko has been working on it though.尼科一直致力于虽然。

If you turn off the leak check by pass +nightly -Zno-leak-check flag, you'll get a saner error message:如果您通过 pass +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,)>`

Now, why does the compiler complain about FnOnce when you actually return Fn ?现在,当您实际返回Fn时,为什么编译器会抱怨FnOnce This is me speculating but it should be reasonable.这是我的推测,但应该是合理的。 FnOnce is a super-trait to Fn , rustc is saying the code doesn't even satisfy the super-trait there's no way it could satisfy the more restrained trait itself. FnOnceFn一个超级特性, rustc说代码甚至不满足超级特性,它无法满足更克制的特性本身。

This finally makes sense now.这现在终于说得通了。 Consider the fact that verify is defined as:考虑这样一个事实, 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,

By asking it to be:通过要求它是:

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

you're asking forall<'b> 'b = 'a which simply isn't possible.你要求forall<'b> 'b = 'a这根本不可能。


Understanding how lifetimes work in Rust is very helpful and almost mandatory.理解生命周期在 Rust 中是如何工作的非常有帮助,而且几乎是强制性的。 When writing your own code though, it is better to skip them whenever possible.但是,在编写自己的代码时,最好尽可能跳过它们。 The lifetime elide rules are there to help you do exactly that.终身省略规则可以帮助您做到这一点。 One certainly doesn't reach for hrtb lifetimes voluntarily :D一个人肯定不会自愿达到 hrtb 的生命周期:D

For example, the more idiomatic way to write your one_token and str_token functions could be:例如,编写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)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM