如何为闭包参数声明更高级别的生命周期?

27

我想在Rust中为闭包声明一个生命周期,但我找不到添加生命周期声明的方法。

use std::str::SplitWhitespace;

pub struct ParserError {
    pub message: String,
}

fn missing_token(line_no: usize) -> ParserError {
    ParserError {
        message: format!("Missing token on line {}", line_no),
    }
}

fn process_string(line: &str, line_number: usize) -> Result<(), ParserError> {
    let mut tokens = line.split_whitespace();

    match try!(tokens.next().ok_or(missing_token(line_number))) {
        "hi" => println!("hi"),
        _ => println!("Something else"),
    }

    // The following code gives "cannot infer appropriate lifetime.....
    // let nt = |t: &mut SplitWhitespace| t.next().ok_or(missing_token(line_number));
    // match try!(nt(&mut tokens)) {
    //     "there" => println!("there"),
    //     _ => println!("_"),
    // }

    // Where should I declare the lifetime 'a?
    // let nt = |t: &'a mut SplitWhitespace| t.next().ok_or(missing_token(line_number));
    // match try!(nt(&mut tokens)) {
    //     "there" => println!("there"),
    //     _ => println!("_"),
    // }

    return Ok(());
}

fn main() {
    process_string("Hi there", 5).ok().expect("Error!!!");
    process_string("", 5).ok().expect("Error!!! 2");
}

Complete sample code on the playground.

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/main.rs:22:42
   |
22 |     let nt = |t: &mut SplitWhitespace| t.next().ok_or(missing_token(line_number));
   |                                          ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 22:14...
  --> src/main.rs:22:14
   |
22 |     let nt = |t: &mut SplitWhitespace| t.next().ok_or(missing_token(line_number));
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: ...so that the types are compatible:
           expected std::iter::Iterator
              found std::iter::Iterator
note: but, the lifetime must be valid for the call at 23:16...
  --> src/main.rs:23:16
   |
23 |     match try!(nt(&mut tokens)) {
   |                ^^^^^^^^^^^^^^^
note: ...so type `std::result::Result<&str, ParserError>` of expression is valid during the expression
  --> src/main.rs:23:16
   |
23 |     match try!(nt(&mut tokens)) {
   |                ^^^^^^^^^^^^^^^

我该如何声明这个闭包的生命周期 'a

也无法管理它。当然,编写 fn 是有效的。fn nt<'a>(t: &'a mut SplitWhitespace, line_number: usize) -> Result<&'a str, ParserError> { t.next().ok_or(missing_token(line_number)) } - tafia
如何为闭包参数指定生命周期? - oli_obk
3个回答

23

正如DK所指出的那样。, 您可以使用一个函数来对闭包的参数和返回值应用额外的约束:

fn constrain<F>(f: F) -> F
where
    F: for<'a> Fn(&'a mut SplitWhitespace) -> Result<&'a str, ParserError>,
{
    f
}

这使您可以完全利用where子句的能力;在此情况下,您可以使用更高级别的特质约束for <...>),表示闭包必须返回与参数相同生命周期的引用。
let nt = constrain(|t| t.next().ok_or(missing_token(line_number)));

归根结底,这是由于Rust类型推断的限制所致。具体而言,如果将闭包立即传递给使用它的函数,则编译器可以推断出参数和返回类型。不幸的是,当它被存储在变量中然后再被使用时,编译器不执行同样级别的推断。

此解决方法之所以有效,是因为它立即将闭包传递给函数,固定了类型和生命周期引用。


12
&mut SplitWhitespace 实际上是一个 &'b mut SplitWhitespace<'a>。这里的相关生命周期是 'a,它指定了 next 返回的字符串切片存在的时间。由于你在 line 参数上应用了 split_whitespace 函数,所以需要将 'a 设置为与 line 参数相同的生命周期。

因此,第一步是向 line 添加一个生命周期:

fn process_string<'a>(line: &'a str, line_number: usize) -> Result<(), ParserError> {

然后,您可以在闭包中将生命周期添加到类型中:

let nt = |t: &mut SplitWhitespace<'a>| t.next().ok_or(missing_token(line_number));

请注意,虽然这回答了你的问题,但你问题的正确解决方案是@A.B.的解决方案。 @A.B.'s solution

9

我不知道如何回答你的问题,但是有两种解决问题的方法:

最简单的方式是让闭包直接引用迭代器。

{
    let mut nt = || tokens.next().ok_or(missing_token(line_number));
    // call the closure as many times as you need to
}
    // At this point `tokens` will be usable again.

如果您不需要在之后执行其他操作,只需执行以下操作:tokens
let mut nt = || tokens.next().ok_or(missing_token(line_number)); 

另一种解决方案是编写一个函数来模拟闭包所做的事情,然后调用该函数。

这个回答解决了问题,而不是回答问题本身;但是@PeterSmit可能正在遭受XY-Problem的困扰。 - oli_obk
1
@ker 可能有点。我既想要一个好的和惯用的解决方案,又想理解为什么某些结构起作用或不起作用。对我来说,目前看来,如果在使用显式生命周期和不使用(例如作用域)之间有选择,大多数情况下最好使用不带显式生命周期的版本。 - Peter Smit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接