【发布时间】:2019-12-04 19:42:38
【问题描述】:
我一直在尝试了解与我编写的返回 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 可以使用任何生命周期的变量。我最初怀疑这可能是由于str_token 上的生命周期参数'a 和错误类型参数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-version、no-error-parameter 或higher-rank-trait-bounds 标签,然后调用cargo run。
注意:
我最近在这里问了一个类似的问题:How to use higher-rank trait bounds to make a returned impl Fn more generic?
但是,我最终认为这个问题对我实际尝试做的事情不够具体。已经有人回答了,所以我不想做大的修改,导致答案变得混乱,显然与我的问题无关。
【问题讨论】:
-
好吧,我不完全理解这个问题,而且 Playground 有一个旧版本的 nom,但我突然想到了一件事情:你有
Fn(&'a str) -> IResult<&str, &str, E>但verify返回impl Fn(I) -> IResult<I, O1, E>;也就是说,函数接受的I必须与它返回的IResult的第一个类型参数相同。如果您将其中一个设为&'a str,则另一个不能只是&str,因为这意味着它们不再是同一类型了。 -
我不必包含
'a因为终身省略。假定其他&str类型的生命周期为'a。 -
你确定吗?据我了解,永远不会假定省略的生命周期与显式生命周期相同。
-
来自关于生命周期省略的 rustnomicon 部分 (doc.rust-lang.org/nomicon/lifetime-elision.html):“如果只有一个输入生命周期位置(是否省略),则该生命周期将分配给所有省略的输出生命周期。”
-
哦,我错了。那么......为什么你决定在参数而不是输出上注释生命周期?如果它是否被省略并不重要,那么在某些地方省略而不是在其他地方省略似乎会令人困惑。也许只有我。
标签: rust