skelley
skelley

Reputation: 507

value "does not live long enough", but only when using a function pointer

I'm trying to get the following simplified code to compile:

type FunctionType<'input> = fn(input_string: &'input str) -> Result<(), &'input str>;

fn okay<'input>(_input_string: &'input str) -> Result<(), &'input str> {
    Ok(())
}

fn do_stuff_with_function(function: FunctionType) {
    let string = String::new();
    match function(string.as_str()) {
        Ok(_) => {},
        Err(_) => {},
    }

}

fn main() {
    do_stuff_with_function(okay);    
}

The playground complains:

error[E0597]: `string` does not live long enough
  --> src/main.rs:13:20
   |
13 |     match function(string.as_str()) {
   |                    ^^^^^^ does not live long enough
...
18 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 11:1...
  --> src/main.rs:11:1
   |
11 | / fn do_stuff_with_function(function: FunctionType) {
12 | |     let string = String::new();
13 | |     match function(string.as_str()) {
14 | |         Ok(_) => {},
...  |
17 | |    
18 | | }
   | |_^

I understand why the error would be firing under other circumstances: string only lives as long as the execution of do_stuff_with_function, but do_stuff_with_function returns the value of function's invocation which includes a same-lifetime reference to its input value, i.e., string.

However, I'm confused on three points:

  1. I match the result of the function call, then return () for both branches. Why does the lifetime of value returned by function matter if it's unconditionally thrown out?
  2. If I replace the call to the parameter function with a direct reference to okay (which has an identical signature), it compiles without complaint.
  3. The error message suggests (though does not state outright?) that the necessary lifetime is already the same as the actual lifetime.

Upvotes: 1

Views: 297

Answers (1)

Kornel
Kornel

Reputation: 100110

TL;DR: Use the for<'a> syntax to have a specific lifetime for the function, rather than take one from the context where it's used.


Lifetime elision on args of do_stuff_with_function hides what's going on here. Your actual lifetime is:

 fn do_stuff_with_function<'a>(function: FunctionType<'a>) {

This lifetime declared in function argument doesn't mean "some random short lifetime I'll come up with later", but rather "a lifetime that already exists at the point of calling this function".

These lifetime annotations are not wildcards, but more like identifiers to track where values came from and where they are going to. They can be used, for example, to clarify a situation like this:

fn do_stuff_with_function<'a>(function: FunctionType<'a>, t: &'a str) {
    match function(t) {
        Ok(_) => {},
        Err(_) => {},
    }
}

fn main() {
    let string = String::new();
    do_stuff_with_function(okay, string.as_str());    
}

However, the problem is solvable in Rust, but just needs a more advanced syntax. For the purpose of the explanation, first, let's change it to:

fn do_stuff_with_function<'a, F>(function: F) where F: Fn(&'a str) -> Result<(), &'a str> {

That means "make a copy of do_stuff_with_function for every unique F which can be anything that looks like a function taking &'a str (etc.). This is essentially the same (+ allows closures) as your code. However, I still had to name the lifetime as tied to the call of do_stuff_with_function<'a>.

So here's a freaky type magic:

fn do_stuff_with_function<F>(function: F) where F: for<'a> Fn(&'a str) -> Result<(), &'a str> {

Which allows moving definition of the lifetime from do_stuff_with_function to definition of the Fn using for<'a> syntax. This way it's specific to the F, rather than do_stuff_with_function arguments.

Upvotes: 2

Related Questions