Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to declare a lifetime for a closure argument?

I would like to declare a lifetime for a closure in Rust, but I can't find a way to add a lifetime declaration.

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)) {
   |                ^^^^^^^^^^^^^^^

How can I declare the lifetime 'a for this closure?

like image 327
Peter Smit Avatar asked Jul 14 '15 10:07

Peter Smit


3 Answers

As originally pointed out by DK., you can use a function to apply extra constraints to a closure's arguments and return values:

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

This gives you the full abilities of the where clause; in this case you can use higher-ranked trait bounds (for <...>) to say that the closure must return a reference of the same lifetime as the argument.

let nt = constrain(|t| t.next().ok_or(missing_token(line_number)));

Ultimately, this is caused due to limitations in Rust's type inference. Specifically, if a closure is passed immediately to a function that uses it, the compiler can infer what the argument and return types are. Unfortunately, when it is stored in a variable before being used, the compiler does not perform the same level of inference.

This workaround works because it immediately passes the closure to a function, nailing down the types and lifetime references.

like image 133
Shepmaster Avatar answered Nov 19 '22 19:11

Shepmaster


The &mut SplitWhitespace is actually a &'b mut SplitWhitespace<'a>. The relevant lifetime here is the 'a, as it specifies how long the string slices that next returns live. Since you applied the split_whitespace function on your line argument, you need to set 'a to the same lifetime that the line argument has.

So as a first step you add a lifetime to line:

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

and then you add the lifetime to the type in your closure:

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

Note that while this answers your question, the correct solution to your Problem is @A.B.'s solution.

like image 12
oli_obk Avatar answered Nov 19 '22 18:11

oli_obk


I don't know how to answer your question, but there are two ways to solve the problem:

The easiest one is to let the closure reference the iterator directly.

{
    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.

If you don't actually need do anything else with tokens afterwards, just do:

let mut nt = || tokens.next().ok_or(missing_token(line_number)); 

The other solution is to write a function that emulates what the closure is doing and call that instead.

like image 9
A.B. Avatar answered Nov 19 '22 20:11

A.B.