How to declare a higher-ranked lifetime for a closure argument?

13,516

Solution 1

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.

Solution 2

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.

Solution 3

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.

Share:
13,516

Related videos on Youtube

Peter Smit
Author by

Peter Smit

Currently working as Doctoral Student in the Speech Group of the Department of Signal Processing and Acoustics of the Aalto Univerity School of Electrical Engineering (formerly TKK / Helsinki University of Technology) in Helsinki, Finland.

Updated on September 15, 2022

Comments

  • Peter Smit
    Peter Smit over 1 year

    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?

    • tafia
      tafia almost 9 years
      Can't manage it either. Of course writing the fn works fn nt<'a>(t : &'a mut SplitWhitespace, line_number: usize) -> Result<&'a str,ParserError> { t.next().ok_or(missing_token(line_number)) }
    • oli_obk
      oli_obk almost 9 years
  • oli_obk
    oli_obk almost 9 years
    this answers the problem, not the question ;), but @PeterSmit is probably suffering from the XY-Problem
  • Peter Smit
    Peter Smit almost 9 years
    @ker Maybe a bit. I both want a good and idiomatic solution and understand why certain constructs work or not. For me it seems at the moment that if there is a choice between using explicit lifetimes or not (e.g. scopes), it is most of the time best to use the version without explicit lifetimes