我一直在尝试了解一些与我编写的返回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-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?
但是,我最终认为这个问题对我实际想做的事情还不够具体。有人已经回答了,所以我不想做大的修改并使答案变得混乱,而且显然与我的问题无关。
答案 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);
}